home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Internet / FTP / Mirror2.3 / mirror < prev    next >
Encoding:
Text File  |  1994-01-31  |  80.1 KB  |  3,385 lines

  1. #!/usr/bin/perl
  2. # Make local directories mirror images of a remote sites
  3. # By Lee McLoughlin <lmjm@doc.ic.ac.uk>
  4. #  You can do what you like with this except claim that you wrote it or
  5. #  give copies with changes not approved by Lee.  Neither Lee nor any other
  6. #  organisation can be held liable for any problems caused by the use or
  7. #  storage of this package.
  8. #
  9. # $Id: mirror.pl,v 2.3 1994/01/31 18:31:22 lmjm Exp lmjm $
  10. # $Log: mirror.pl,v $
  11. # Revision 2.3  1994/01/31  18:31:22  lmjm
  12. # Allow for funny chars in filenames when calling the shell (Erez).
  13. # Added compress_size_floor to avoid compressing small files (David).
  14. # Added get_missing to just delete files not on remote system (Pieter).
  15. # Don't try to delete old dirs if no time set (Pieter).
  16. # Zap .dir$$ files, and keep then in $big_temp.
  17. # Pretty print time in comparisons.
  18. # Move the large comparision conditionals into routines (David).
  19. # Allow for sites with limited filename lengths.
  20. # Allow for deleted files when doing deletes.
  21. # Don't delete dirs that are really symlinks.
  22. #
  23. # Revision 2.2  1993/12/14  11:09:15  lmjm
  24. # Allow for no flock.
  25. # Use installed socket.ph.
  26. # Allow for system 5.
  27. # Use percentage defaults on max_delete_*
  28. # Checkout regexps before using.
  29. # Allow for extra leading | in local_ignore.
  30. # Return better exit codes.
  31. # Fixups for recurse_hard.
  32. # Smarter symlink handling.
  33. #
  34. # Revision 2.1  1993/06/28  14:59:00  lmjm
  35. # Full 2.1 release
  36. #
  37. #
  38.  
  39. # Default settings file loaded from a directory in PERLLIB
  40. $defaults_file = 'mirror.defaults';
  41. $load_defaults = 1;
  42.  
  43. # Try to find the default localation of various programs via
  44. # the users PATH then using $extra_path
  45. $extra_path = '/usr/local/bin:/usr/new/bin:/usr/public/bin:/usr/ucb:/usr/bin:/bin:/etc:/usr/etc:/usr/local/etc:';
  46. $ENV{ 'PATH' } .= ':' . $extra_path;
  47.  
  48. # If compressing a local file to send need somewhere to store the temp
  49. # compressed version.
  50. $big_temp = '/var/tmp';
  51.  
  52. # Hopefully we have flock.
  53. $can_flock = 1;
  54.  
  55. # NOTE:
  56. #  It is not an error for a program not to be found in the path as the user
  57. # may be setting it as part of the package details or defaults.
  58.  
  59. # Used by the save_deletes option
  60. $mv_prog = 'mv -f';
  61.  
  62. # compress must be able to take the -d arg to cause it to uncompress.
  63. $sys_compress_prog = &find_prog( 'compress' ) ||
  64.     die "No compress command in path\n";
  65. $sys_compress_suffix = 'Z';
  66. # Like compress gzip must be able to take -d
  67. if( $gzip_prog = &find_prog( 'gzip' ) ){
  68.     # Force maximum compression with gzip
  69.     $gzip_level = ' -9';
  70.     $gzip_prog .= $gzip_level;
  71.     $gzip_suffix = 'gz';
  72.     $old_gzip_suffix = 'z';
  73. }
  74.  
  75. # A mail program that can be called as: "$mail_prog person_list'
  76. # Can be overridden with the mail_prog keyword.
  77. # If you use $mail_subject to pass extra arguments then remember that
  78. # the mail program will need to know how to handle them.
  79. $mail_prog = &find_prog( 'mailx' );
  80. if( ! $mail_prog ){
  81.     $mail_prog = &find_prog( 'Mail' );
  82. }
  83. if( ! $mail_prog ){
  84.     $mail_prog = &find_prog( 'mail' );
  85. }
  86. $current_mail_to = '';        # Keep track of who mail is being sent to.
  87.  
  88. # Used to remove directory heirarchies.  This program is passed the -rf
  89. # arguments.
  90. $rm_prog = &find_prog( 'rm' );
  91.  
  92. # Generate checksums
  93. $sum_prog = &find_prog( 'sum' );
  94.  
  95. # SPECIAL NOTE: This is eval'd, so DONT put double-quotes (") in it.
  96. # You can get local variables to appear as in the second example:
  97. $mail_subject = '-s \'mirror update\'';
  98. # $mail_subject = ' -s \'mirror update of $package\'';
  99.  
  100. # When scanning the local directory, how often to prod the remote
  101. # system to keep the connection alive
  102. $prod_interval = 60;
  103.  
  104. # Put the directory that mirror is actually in at the start of PERLLIB.
  105. $dir = &real_dir_from_path( $0 );
  106. unshift( @INC, $dir );
  107.  
  108. # Make sure that your PERLLIB environment variable can get you
  109. # all these or that they are installed.
  110. require 'sys/socket.ph';
  111. require 'ftp.pl';
  112. require 'lsparse.pl';
  113. require 'dateconv.pl';
  114.  
  115. # Find some local details
  116. chop( $home = `pwd` );
  117. chop( $host = `(hostname || uname -n || uuname -l) 2>/dev/null` );
  118. if( $hn = (gethostbyname( "$hostname" ))[ 0 ] ){
  119.     $hostname = $hn;
  120. }
  121.  
  122. # Some systems hold the username in $USER, some in $LOGNAME.
  123. $me = $ENV{'USER'} || $ENV{'LOGNAME'};
  124.  
  125. # Files matching this pattern are usually compressed.
  126. $squished = '\.(Z|z|gz)$';
  127.  
  128. # special /bin/sh chars that must be escaped.
  129. $shell_metachars = '\"|\$|`|\\\\';
  130.  
  131. # Remote directory parsing fail if not given input every readtime seconds.
  132. $parse_time = 600;
  133.  
  134. # Timeout are not fatal unless you get more than this number of them.
  135. $max_timeouts = 20;
  136.  
  137. # If connected to a site then this holds the site name.
  138. $connected = '';
  139.  
  140. # Umask setting.
  141. $curr_umask = sprintf( "0%o", umask );
  142.  
  143. # mapping from a pathname to a number - just to make the keys to assoc arrays
  144. # shorter.
  145. $map_init = 1;    # just so I know 0 is invalid
  146.  
  147. $tmp = "/tmp";
  148. @assocs = ( 'local_map', 'remote_map' );
  149.  
  150. # Create a reasonable set of defaults
  151. $default{ 'package' } = '';    # should be a unique handle for the "lump" to be mirrored
  152. $default{ 'comment' } = '';    # General comment used in report
  153. $default{ 'skip' } = '';    # If set then skip this entry giving value as reason
  154. $default{ 'site' } = '';    # site to connect to
  155. $default{ 'remote_dir' } = '';    # remote directory to mirror
  156. $default{ 'local_dir' } = '';    # local directory to copy into
  157. $default{ 'remote_user' } = 'anonymous';  # the remote guest account name
  158. $default{ 'remote_password' } = "$me@$hostname";
  159. $default{ 'get_patt' } = ".";    # regex of pathnames to retrieve
  160. $default{ 'exclude_patt' } = ''; # regex of pathnames to ignore
  161. $default{ 'update_local' } = 0;    # Don't just update local dirs
  162. $default{ 'local_ignore' } = ''; # regex of local pathnames to totally ignore
  163. $default{ 'do_deletes' } = 0;    # delete dest files if not in src tree
  164. $default{ 'delete_excl' } = ''; # regex of local pathnames to ignore when deleting
  165. $default{ 'delete_patt' } = '.';# delete only files which match this pattern
  166. $default{ 'delete_get_patt' } = 0;# true: set delete_patt to get_patt
  167. $default{ 'save_deletes' } = 0;    # save local files if not in remote
  168. $default{ 'save_dir' } = 'Old';    # directory in which to create tree for keeping
  169.                 # files no longer in remote
  170. $default{ 'max_delete_files' } = '10%'; # Any more than this and DONT delete
  171. $default{ 'max_delete_dirs' } = '10%'; # Any more than this and DONT delete
  172. $default{ 'max_days' } = 0;    # Ignore age of file
  173. $default{ 'split_max' } = 0;    # Files > this size can be split up.
  174. $default{ 'split_patt' } = '';  # Files must match this pattern to be split
  175. $default{ 'split_chunk' } = 100 * 1024; # Size of split-up chunks
  176. $default{ 'ls_lR_file' } = '';    # remote file containing ls-lR - else use remote ls
  177. $default{ 'local_ls_lR_file' } = ''; # local file containing ls-lR
  178.                 # useful when first copying a large remote package
  179. $default{ 'recursive' } = 1;    # true indicates to do recursive processing
  180. $default{ 'recurse_hard' } = 0;    # true indicates have to cwd+ls for each remote
  181.                 # subdirectory - AVOID wherever possible.
  182. $default{ 'flags_recursive' } = '-lRat'; # Flags passed to remote dir
  183. $default{ 'flags_nonrecursive' } = '-lat'; # Flags passed to remote dir
  184. $default{ 'name_mappings' } = '';# remote to local pathname mappings
  185.                  # (eg s:old:new)
  186. $default{ 'external_mapping' } = '';# remote to local mapping by external routine
  187. $default{ 'get_newer' } = 1;    # get remote file if its date is newer than local
  188. $default{ 'get_size_change' } = 1; # get the file if size if different than local
  189. $default{ 'compress_patt' } = ''; # compress files matching this pattern
  190. $default{ 'compress_excl' } = $squished; # dont compress regexp (case insensitive)
  191. $default{ 'compress_prog' } = $sys_compress_prog; # Program to compress files.
  192. $default{ 'compress_suffix' } = $sys_compress_suffix; # Suffix on compressed files
  193. $default{ 'compress_conv_patt' } = '(\.Z|\.taz)$';
  194.     # compress->gzip files matching this pattern
  195. $default{ 'compress_conv_expr' } = 's/\.Z$/.gz/;s/\.taz$/.tgz/';
  196.     # perl expressions to convert names of files from compress->gzip
  197. $default{ 'compress_size_floor' } = 0;  # don't compress files < this size
  198. $default{ 'force_times' } = 1;    # Force local file times to match the original
  199. $default{ 'retry_call' } = 1;    # Retry the call if it fails first time
  200. $default{ 'update_log' } = '';    # Filename where an update report is to be kept
  201. $default{ 'mail_to' } = '';    # Mail a report to these addresses
  202. $default{ 'user' } = '';    # UID/user name to give to local pathnames
  203. $default{ 'group' } = '';    # GID/group name to give to local pathnames
  204. $default{ 'file_mode' } = 0444;    # Mode to give files created locally
  205. $default{ 'dir_mode' } = 0755;    # mode to give directories created locally
  206. $default{ 'timeout' } = 120;    # timeout ftp requests after this many seconds
  207. $default{ 'ftp_port' } = 21;    # port number of remote ftp daemon
  208. $default{ 'proxy' } = 0;    # normally use regular ftp
  209. $default{ 'proxy_ftp_port' } = 4514; # default from Sun
  210. $default{ 'proxy_gateway' } = $ENV{ 'INTERNET_HOST' };    # used if($proxy) 
  211. $default{ 'mode_copy' } = 0;    # true indicates to copy the mode bits
  212. $default{ 'interactive' } = 0;    # noninteractive copy default
  213. $default{ 'text_mode' } = 0;    # transfer in binary mode by default
  214. $default{ 'force' } = 0;    # don't force by default
  215. $default{ 'get_file' } = 1;    # perform get, not put by default
  216. $default{ 'verbose' } = 0;    # Verbose messages
  217. $default{ 'remote_fs' } = 'unix'; # Remote filestore
  218.     # Other posibilies dls, netware and vms
  219. $default{ 'vms_keep_versions' } = 1; # Keep multiple VMS versions
  220. $default{ 'vms_xfer_text' } = 'readme$|info$|listing$|\.c$';
  221.                 # pattern of VMS files to xfer in TEXT mode
  222.                 # (Case insensitive)
  223. $default{ 'delete_source' } = 0;# delete source after xfer (default = NO!!!)
  224. $default{ 'disconnect' } = 0;    # Force close at end of package EVEN if
  225.                 # next package is to the same site
  226. $default{ 'mail_prog' } = $mail_prog; # the mail program (see $mail_prog)
  227. $default{ 'mail_subject' } = $mail_subject; # Subject passed to mail_prog
  228. $default{ 'hostname' } = $hostname; # The LOCAL hostname
  229. $default{ 'umask' } = 07000; # DONT allow setuid things by default
  230. # If mirroring a VERY large directory then it is best to put the assoc
  231. # arrays in files (use command line switch -F. to turn on).
  232. $default{ 'use_files' }  = 0;
  233. # Use local time NOT gmt to timestamp files.
  234. # The original mirror got it wrong you should be using localtime
  235. # This variable is only here to allowed packages to be switched over one by
  236. # one.  (See also the -T flag.)
  237. $default{ 'use_timelocal' }  = 1;
  238. # Used for group and gpass.  (As in ftp.1 site group/gpass commands.)
  239. $default{ 'remote_group' } = '';
  240. $default{ 'remote_gpass' } = '';
  241. # Set the remote idle timer to this
  242. $default{ 'remote_idle' } = '';
  243. # prevent symlinks to non-existant files
  244. $default{ 'make_bad_symlinks' } = 0;
  245. # Follow symlinks to pathnames matching this regexp.
  246. $default{ 'follow_local_symlinks' } = '';
  247. # Set get_missing to 0 to just delete files not on remote system
  248. $default{ 'get_missing' } = 1;
  249.  
  250. @boolean_values = ( 'get_newer', 'get_size_change', 'do_deletes',
  251.     'update_local',    'force_times', 'retry_call', 'recursive',
  252.     'mode_copy', 'disconnect', 'interactive', 'text_mode',
  253.     'force', 'get_file', 'verbose', 'proxy', 'delete_get_patt',
  254.     'delete_source', 'save_deletes', 'use_files', 'use_timelocal',
  255.     'make_bad_symlinks', 'recurse_hard', 'get_missing' );
  256. %boolean_values = ();
  257. &set_assoc_from_array( *boolean_values );
  258.  
  259. @regexp_values = ( 'get_patt', 'exclude_patt', 'local_ignore',
  260.           'delete_patt', 'delete_excl', 'split_patt', 'save_deletes',
  261.           'compress_patt', 'compress_excl', 'compress_conv_patt' );
  262.  
  263. #
  264. # message levels used by &msg( level, msg )
  265. # if you call msg as &msg( msg ) the level is presumed to be just $pr.
  266. $pr = 0;    # Always print out messages
  267. $log = 1;    # push this messages onto @log
  268.  
  269. #
  270. # Exit status
  271. $exit_status = 0;
  272. $exit_status_xfers = 0;
  273.  
  274. # "#defines" for the above
  275. $exit_xfers = 16;  # Add this to the exit code to show xfers took place
  276. $exit_ok = 0;
  277. $exit_fail = 1;
  278. $exit_fail_noconnect = 2;
  279.  
  280. # -d        Turn on debugging - more -d's means more debugging.
  281. # -ppattern    Just do packages matching pattern.
  282. # -Rpattern    Skip till the first package name matches pattern then do all.
  283. #        it and following packages.
  284. # -n        Do nothing, just show what would be done.
  285. # -F        Use files for assoc arrays (see also the variable use_files).
  286. # -gsite:path
  287. #        Get all files on given site.  If path matches .*/.+ then
  288. #        it is the name of the directory and the last part is the
  289. #        pattern of filenames to get.  If path matches .*/ then
  290. #        it is the name of a directory and all its contents are retrieved.
  291. #        Otherwise path is the pattern to be used in '/'.
  292. # -r        Same as "-krecursive=false".
  293. # -kvar=val    set variable to value.
  294. # -uusername    Same as "-kremote_user=username", prompts for remote_password.
  295. # -v        Print version and exit.
  296. # -T        Dont transfer just force local timestamps to match remote.
  297. # -N        Don't load mirror.defaults.
  298. # -L        Generate a pretty list of what is being mirrored.
  299. # -m         Same as "-kmode_copy=true".
  300.  
  301. # -Cconfig_file
  302. # -P         Same as "-kget_file=false -kinteractive=true".
  303. # -G        Same as "-kget_file=true -kinteractive=true".
  304. # -t         Same as "-ktext_mode=true".
  305. # -f        Same as "-kforce=true".
  306. # -sSITENAME    Same as "-ksite=SITENAME.
  307. # -ULOGFILE    Set the upload log to LOGILE - if none given uses
  308. #        the file $home/upload_log.$mday.$mon.$year
  309.  
  310. # -DUMP        Dump perl - to be later undumped --  THIS DOES NOT YET WORK!!!
  311.  
  312. sub msg_version
  313. {
  314.     &msg( '$Id: mirror.pl,v 2.3 1994/01/31 18:31:22 lmjm Exp lmjm $' . "\n" );
  315. }
  316.  
  317. parse_args:
  318. while( $ARGV[ 0 ] =~ /^-/ ){
  319.     local( $arg ) = shift;
  320.  
  321.     if( $arg eq '-d' ){
  322.         if( $debug == 2 ){
  323.             &msg_version();
  324.         }
  325.         $| = 1;
  326.         $debug++;
  327.         next;
  328.     }
  329.  
  330.     if( $arg =~ /^-(p)(.*)/ || $arg =~ /^-(R)(.*)/ ){
  331.         local( $flag, $p ) = ($1, $2);
  332.         if( $flag eq 'R' ){
  333.             # Skip all packages till a match is made
  334.             # then process ALL further packages
  335.             $skip_till = 1;
  336.         }
  337.         if( ! $p ){
  338.             # Must be -p/-R space arg
  339.             $p = shift;
  340.         }
  341.         if( $p !~ /[a-zA-Z0-9]/ ){
  342.             die "Invalid package name to -p of: $p\n";
  343.             next;
  344.         }
  345.         # Only mirror the named packages
  346.         $do_packages{ $p } = 1;
  347.         $limit_packages = 1;
  348.         next;
  349.     }
  350.  
  351.     if( $arg eq '-n' ){
  352.         # Do nothing - just show what would be done
  353.         $dont_do = 1;
  354.         $debug += 2;
  355.         $| = 1;
  356.         next;
  357.     }
  358.  
  359.     if( $arg eq '-F' ){
  360.         # Use files for the dir listings assoc lookups
  361.         $use_files = 1;
  362.         $command_line{ 'use_files' } = 1;
  363.         next;
  364.     }
  365.  
  366.     if( $arg eq '-T' ){
  367.         # Don't actually get any files but just force
  368.         # local timestamps to be the same on the remote system
  369.         $timestamp = 1;
  370.         $command_line{ 'force_times' } = 'true';
  371.         next;
  372.     }
  373.  
  374.     if( $arg =~ /^-g(.*)$/ ){
  375.         # the next arg is the site:path to get
  376.         local( $site_path ) = $1;
  377.  
  378.         if( ! $site_path ){
  379.             # Must be -g space arg
  380.             $site_path = shift;
  381.         }
  382.         
  383.         # DONT use the system defaults!
  384.         $load_defaults = 0;
  385.         
  386.         # This is probably interactive so print interactively
  387.         $| = 1;
  388.         
  389.         if( $site_path =~ /(.*):(.*)?/ ){
  390.             local( $site, $path ) = ($1, $2);
  391.             push( @get_sites, $site );
  392.             # Find the directory and files
  393.             if( $path =~ m|^(.*)/([^/]*)$| ){
  394.                 if( $1 eq '' ){
  395.                     push( @get_paths, '/' );
  396.                 }
  397.                 else {
  398.                     push( @get_paths, $1 );
  399.                 }
  400.                 if( $2 eq '' ){
  401.                     push( @get_paths, $1 );
  402.                     push( @get_patt, '.' );
  403.                 }
  404.                 else {
  405.                     push( @get_patt, "^$2$" );
  406.                 }
  407.             }
  408.             else {
  409.                 push( @get_paths, '.' );
  410.                 push( @get_patt, "^$path$" );
  411.             }
  412.         }
  413.         else {
  414.             die "expected -gsite:path got $arg";
  415.         }
  416.         next;
  417.     }
  418.  
  419.     if( $arg eq "-r" ){
  420.         # no recursive copy
  421.         $command_line{ 'recursive' } = 0;
  422.         next;
  423.     }
  424.  
  425.     if( $arg =~ /^-k(.*)=(.*)/ ){
  426.          # set the keyword = value
  427.         if( !defined( $default{ "$1" } ) ){
  428.             warn "Invalid keyword $1\n";
  429.         } else {
  430.             $command_line{ "$1" } = $2;
  431.         }
  432.         next;
  433.     }
  434.  
  435.     if( $arg =~ /^-u(.*)/ ){
  436.         local( $user ) = $1;
  437.  
  438.         if( ! $user ){
  439.             # must be -u space user
  440.             $user = shift;
  441.         }
  442.  
  443.         # override the user name
  444.             $command_line{ 'remote_user' } = $user;
  445.         # and ask for a password
  446.         $command_line{ 'remote_password' } = &get_passwd( $user );
  447.         next;
  448.     }
  449.  
  450.     if( $arg eq '-N' ){
  451.         $load_defaults = 0;
  452.         next;
  453.     }
  454.  
  455.     if( $arg eq '-v' ){
  456.         &msg_version();
  457.         exit( 0 );
  458.     }
  459.  
  460.         if( $arg eq '-L' ){
  461.                 # Generate a pretty list of what is being mirrored
  462.                 $pretty_print = 1;
  463.                 next;
  464.         }
  465.  
  466.         if( $arg eq '-m' ){
  467.                 # propagate the mode
  468.         $command_line{ 'mode_copy' } = 'true';
  469.         next;
  470.         }
  471.  
  472.     # Old command line interface flags
  473.     if( $arg =~ /^-C(.*)/ ){
  474.         # specify the config file
  475.         local( $c ) = $1;
  476.         if( $c !~ /./ ){
  477.             die "Must give config file name -Cname ($arg)\n";
  478.         }
  479.         # Only mirror the named packages
  480.             push( @config_files, $c);
  481.         next;
  482.     }
  483.  
  484.         if( $arg eq '-P' ){
  485.                 # put files
  486.         $command_line{ 'get_file' } = 'false';
  487.         $command_line{ 'interactive' } = 'true';
  488.         next;
  489.         }
  490.  
  491.         if( $arg eq '-G' ){
  492.                 # get files
  493.         $command_line{ 'get_file' } = 'true';
  494.         $command_line{ 'interactive' } = 'true';
  495.         next;
  496.         }
  497.  
  498.         if( $arg eq '-t' ){
  499.                 # set the file mode to text
  500.         $command_line{ 'text_mode' } = 'true';
  501.         next;
  502.         }
  503.  
  504.         if( $arg eq '-f' ){
  505.                 # force file transfers irregardless of date/size matches
  506.         $command_line{ 'force' } = 'true';
  507.         next;
  508.         }
  509.  
  510.     if( $arg =~ /^-s(.*)/ ){
  511.         # override the site name
  512.         $command_line{ 'site' } = $1;
  513.         next;
  514.     }
  515.  
  516.     if( $arg =~ /^-U(.*)/ ){
  517.         $upload_log = $1;
  518.         if( $upload_log eq '' ){
  519.             local( $sec,$min,$hour,$mday,$mon,$year,
  520.                 $wday,$yday,$isdst ) 
  521.                 = localtime( time );
  522.             $mon++;
  523.             $upload_log = "$home/upload_log.$mday.$mon.$year";
  524.         }
  525.             
  526.         next;
  527.     }
  528.  
  529.     if( $arg eq '-DUMP' ){
  530.         # THIS DOES NOT YET WORK!!!!!
  531.         $dumped_version = 1;
  532.         warn "Dumping perl\n";
  533.         dump parse_args;
  534.     }
  535.  
  536.     warn "Unknown arg $arg, skipping\n";
  537. }
  538.  
  539. # Handle multi-line buffers in a sane way
  540. $* = 1;
  541.  
  542. $interactive = $command_line{ 'interactive' };
  543.  
  544. if( ! $interactive ){
  545.     local( $c );
  546.  
  547.     # The remainder of ARGV are package names
  548.     foreach $c ( @ARGV ){
  549.         push( @config_files, $c );
  550.     }
  551. }
  552.  
  553. if( $interactive && $limit_packages){
  554.     die "Can not mix -p and interactive";
  555. }
  556.  
  557. $value{ 'remote_user' } = $default{ 'remote_user' };
  558. %value = ();
  559. &set_defaults();
  560.  
  561. if( $load_defaults ){
  562.     local( $dir, $mp );
  563.     foreach $dir ( @INC ){
  564.         local( $f ) = "$dir/$defaults_file";
  565.         if( -f $f ){
  566.             $mp = $f;
  567.             last;
  568.         }
  569.     }
  570.     if( $mp ){
  571.         &msg( "defaults from $mp\n" ) if $debug > 2;
  572.         splice( @config_files, 0, 0, $mp );
  573.     }
  574.     else {
  575.         warn "No $defaults_file found in perl library path\n";
  576.     }
  577. }
  578. elsif( $debug > 1 ){
  579.     &msg( "not loading $defaults_file\n" );
  580. }
  581.         
  582.  
  583. &interpret_config_files();
  584.  
  585. # Shut down any remaining ftp session
  586. &disconnect();
  587.  
  588. &msg( "All done, Exiting\n" ) if $debug;
  589. exit( $exit_status + $exit_status_xfers );
  590.  
  591.  
  592. $key = ''; # The current keyword
  593. $value = ''; # the value for the keyword
  594.  
  595. sub interpret_config_files
  596. {
  597.     local( $fname );
  598.  
  599.     if( $#get_sites >= 0 ){
  600.         while( $#get_sites >= 0 ){
  601.             $value{ 'site' } = pop( @get_sites );
  602.             $value{ 'remote_dir' } = pop( @get_paths );
  603.             $value{ 'get_patt' } = pop( @get_patt );
  604.             $value{ 'local_dir' } = '.';
  605.             $value{ 'remote_user' } = 'anonymous';
  606.             $exit_status = &do_mirror();
  607.         }
  608.         return;
  609.     }
  610.         
  611.  
  612.     if( $command_line{ 'interactive' } ){
  613.         # No config file to read
  614.         $value{ 'package' } = 'interactive';
  615.         $exit_status = &do_mirror();
  616.         return;
  617.     }
  618.  
  619.     # if no configuration files were specified use standard input
  620.     @ARGV = @config_files;
  621.     &interpret_config();
  622. }
  623.  
  624. sub interpret_config
  625. {
  626.     while( <> ){
  627.         # Ignore comment and blank lines
  628.         next if /^\s*#/ || /^\s*$/;
  629.         
  630.         &parse_line();
  631.         
  632.         # Is this a new package?
  633.         if( $value{ 'package' } && $key eq 'package' ){
  634.             # mirror the existing package
  635.             $exit_status = &do_mirror();
  636.             
  637.             # reset
  638.             &set_defaults();
  639.  
  640.             # Make sure I'm at the right place for <> to work!
  641.             chdir $home;
  642.         }
  643.         
  644.         if( $debug > 3 ){
  645.             &msg( "$key \"$value\"\n" );
  646.         }
  647.  
  648.         $value{ $key } = $value;
  649.  
  650.         # do an explicit close for each file so $. gets reset
  651.         if( eof( ARGV ) ){
  652.             if( $debug > 3 ){
  653.                 &msg( "-- end of config file \"$ARGV\"\n" );
  654.             }
  655.             close( ARGV );
  656.         }
  657.     }
  658.  
  659.     # Mirror the last package in the file
  660.     if( $value{ 'package' } ){
  661.         $exit_status = &do_mirror();
  662.     }
  663. }
  664.  
  665. # parse each line for keyword=value
  666. sub parse_line
  667. {
  668.     local( $eqpl );
  669.     local( $cont ) = '&';
  670.  
  671.     chop;
  672.     if( /^\s*([^\s=+]+)\s*([=+])(.*)?$/ ){
  673.         ($key, $eqpl, $value) = ($1, $2, $3);
  674.         # If the value ends in the continuation character then
  675.         # tag the next line on the end (ignoring any leading ws).
  676.         while( $value =~ /^(.*)$cont$/o && !eof ){
  677.             $_ = <>;
  678.             local( $v ) = $1;
  679.             if( /^\s*(.*)$/ ){
  680.                 $value = $v . $1;
  681.             }
  682.         }
  683.         if( $debug > 3 ){
  684.             &msg( "read: $key$eqpl$value\n" );
  685.         }
  686.     }
  687.     else {
  688.         warn "unknown input in \"$ARGV\" line $. of: $_\n";
  689.     }
  690.     if( ! defined( $default{ "$key" } ) ){
  691.         die "unknown keyword in \"$ARGV\" line $. of: $key\n";
  692.     }
  693.     if( $eqpl eq '+' ){
  694.         $value = $value{ $key } . $value;
  695.     }
  696. }
  697.  
  698. # Initialise the key values to the default settings
  699. sub set_defaults
  700. {
  701.     %value = %default;
  702. }
  703.  
  704. # Override the current settings with command line values
  705. sub command_line_override
  706. {
  707.     local( $key, $val, $overrides );
  708.  
  709.     while( ($key, $val) = each %command_line ){
  710.         $overrides++;
  711.         if( $boolean_values{ $key } ){
  712.             # a boolean value
  713.             $value{ $key } = &istrue( $val );
  714.         } else {
  715.             # not a boolean value
  716.             $value{ $key } = $val;
  717.         }
  718.     }
  719.  
  720.     if( $debug > 4 ){
  721.         if( $overrides ){
  722.             &pr_variables( "keywords after command line override\n" );
  723.         }
  724.         else {
  725.             &msg( "No command line overrides\n" );
  726.         }
  727.     }
  728. }
  729.  
  730. # set each variable $key = $value{ $key }
  731. sub set_variables
  732. {
  733.     local( $key, $val );
  734.  
  735.     while( ($key, $val) = each %value ){
  736.         # for things like passwords it is nice to have the
  737.         # real value in a file
  738.         if( $val =~ /^\<(.*)$/ ){
  739.             local( $val_name ) = $1;
  740.             open( VAL_FILE, $val_name ) ||
  741.                 die "can't open value file $val_name\n";
  742.             $val = <VAL_FILE>;
  743.             close( VAL_FILE );
  744.             chop $val if $val =~ /\n$/;
  745.         }
  746.  
  747.         if( $boolean_values{ $key } ){
  748.             # a boolean value
  749.             eval "\$$key = &istrue( $val )";
  750.         }
  751.         else {
  752.             # not a boolan value
  753.             # Change all \ to \\ since \'s will be escaped in
  754.             # the following string used in the eval.
  755.             $val =~ s/([^\\])(')/$1\\$2/g;
  756.             eval "\$$key = '$val'";
  757.         }
  758.         if( $key eq 'compress_prog' ){
  759.             if( $val eq 'compress' ){
  760.                 $compress_prog = $sys_compress_prog;
  761.                 $compress_suffix = $sys_compress_suffix;
  762.             }
  763.             elsif( $val eq 'gzip' ){
  764.                 if( ! $gzip_prog ){
  765.                     die "Trying to use gzip but not found in PATH\n";
  766.                 }
  767.                 $compress_prog = $gzip_prog;
  768.                 $compress_suffix = $gzip_suffix;
  769.             }
  770.             elsif( $debug > 2 && $compress_prog ne $gzip_prog &&
  771.                    $compress_prog ne $sys_compress_prog ){
  772.                 &msg( "compress_prog not compress or gzip, presuming program name\n" .
  773.                       "- user must set compress_suffix\n" );
  774.             }
  775.             &upd_val( 'compress_prog' );
  776.             &upd_val( 'compress_suffix' );
  777.         }
  778.     }
  779.  
  780.     # Reset the umask if needed.
  781.     # Do it here to try and get it done as early as possible.
  782.     # If the user doesn't use octal umasks this will cause umask
  783.     # to be called again unnecessarily - but that is pretty cheap.
  784.     if( $umask && $umask != $curr_umask ){
  785.         local( $val ) = $umask;
  786.         $val = oct( $val ) if $val =~ /^0/;
  787.         umask( $val );
  788.         $curr_umask = sprintf( "0%o", umask );
  789.     }
  790. }
  791.  
  792. sub upd_val
  793. {
  794.     local( $key ) = @_;
  795.     if( $package eq 'defaults' ){
  796.         $default{ $key } = $value{ $key };
  797.     }
  798. }
  799.  
  800. sub pr_variables
  801. {
  802.     local( $msg ) = @_;
  803.     local( $nle ) = 60;
  804.     local( $out ) = 0;
  805.     local( $key, $val, $str );
  806.  
  807.     &msg( $msg );
  808.     &msg( "package=$package  $site:$remote_dir -> $local_dir\n\t" );
  809.  
  810.     for $key ( sort keys( %value ) ){
  811.         next if $key eq 'package' ||
  812.             $key eq 'site' ||
  813.             $key eq 'remote_dir' ||
  814.             # Don't show passwords when interactive
  815.             ($interactive && $key eq 'remote_password') ||
  816.             ($interactive && $key eq 'remote_gpass');
  817.         # Report the value in the actual variable
  818.         $val = eval "\$$key";
  819.         $str = "$key=\"$val\" ";
  820.         &msg( $str );
  821.         $out += length( $str );
  822.         # Output newlines when a line is full
  823.         if( $out > $nle ){
  824.             $out = 0;
  825.             &msg( "\n\t" );
  826.         }
  827.     }
  828.     &msg( "\n" );
  829. }
  830.  
  831. # Mirror the package, return exit_status
  832. sub do_mirror
  833. {
  834.     $package = $value{ 'package' };
  835.     
  836.     if( $package eq 'defaults' ){
  837.         # This isn't a real site - just a way to change the defaults
  838.         %default = %value;
  839.  
  840.         return $exit_ok;
  841.     }
  842.  
  843.     # Only do this package if given by a -Ppack argument
  844.     if( $limit_packages && ! $do_packages{ $package } ){
  845.         return;
  846.     }
  847.  
  848.     if( $skip_till ){
  849.         # Found a package so process all packages from now on
  850.         $skip_till = $limit_packages = 0;
  851.     }
  852.     
  853.     local( $exit_status ) = $exit_fail_noconnect;  # Presume the worse.
  854.     $timeouts = 0;
  855.  
  856.     # set things from the command line arguments
  857.     &command_line_override();
  858.  
  859.     if( ! &checkout_regexps() ){
  860.         &msg( "skipping package\n\n" );
  861.         return $exit_status;
  862.     }
  863.  
  864.     # set each variable $key = $value{ $key }
  865.     &set_variables();
  866.  
  867.     # don't trash locally glossed over things with stuff from the remote
  868.     if( $local_ignore ){
  869.         if( $exclude_patt ){
  870.             $exclude_patt .= '|' . $local_ignore;
  871.         }
  872.         else {
  873.             $exclude_patt = $local_ignore;
  874.         }
  875.     }
  876.  
  877.     if( $debug > 3 ){
  878.         &pr_variables( "\n" );
  879.     }
  880.     elsif( $package && ! $pretty_print ){
  881.         &msg( "package=$package $site:$remote_dir -> $local_dir\n");
  882.     }
  883.     
  884.     # Don't bother if trying to mirror here!
  885.     if( !$interactive && !$force && ((gethostbyname( $site ))[0] eq $hostname) ){
  886.         &msg( "Skipping $site as it is this local site!\n\n" );
  887.         return $exit_ok;
  888.     }
  889.  
  890.     chdir $home;
  891.  
  892.     $max_age = 0;
  893.     if( $value{ 'max_days' } ne '0' ){
  894.         $max_age = time - ($value{ 'max_days' } * 24 * 60 * 60);
  895.         &msg( "max_age = $max_age\n" ) if $debug > 1;
  896.     }
  897.  
  898.     # pull in external code, if required
  899.     if( $external_mapping ){
  900.         &msg( "Loading external mapping from $external_mapping.\n" ) if $debug > 0 ;
  901.         do $external_mapping || die "Cannot load from $external_mapping";
  902.     }
  903.  
  904.     if( $debug ){
  905.         # Keep the ftp debugging lower than the rest.
  906.         &ftp'debug( $debug - 1);
  907.     }
  908.     else {
  909.         &ftp'debug( $verbose );
  910.     }
  911.  
  912.     if( $recurse_hard ){
  913.         $recursive = 1;
  914.     }
  915.  
  916.     if( ! $interactive ){
  917.         $ftp'showfd = 'STDOUT';
  918.     }
  919.     &ftp'set_timeout( $timeout );
  920.     &ftp'set_signals( "main'msg" );
  921.  
  922.     # Useful string in prints
  923.     $XFER = $get_file ? "get" : "put";
  924.  
  925.     # create the list of items to copy
  926.     @transfer_list = ();
  927.     if( $interactive ){
  928.         # copy the remainder of items from argv to the transfer list
  929.         while( @ARGV ){
  930.             # copy the local directory
  931.             if( @ARGV ){
  932.                 push( @transfer_list, shift( @ARGV ) );
  933.             } 
  934.     
  935.             # copy the remote directory
  936.             if( @ARGV ){
  937.                 push( @transfer_list, shift( @ARGV ) );
  938.             }
  939.             else {
  940.                 die "remote directory must be specified\n";
  941.             }
  942.     
  943.             # copy the pattern, if available
  944.             if( @ARGV ){
  945.                 push( @transfer_list, shift( @ARGV ) );
  946.             } else {
  947.                 push( @transfer_list, $default{ 'get_patt' } );
  948.             }
  949.         }
  950.     
  951.         if( $debug > 1 ){
  952.             local( @t );
  953.             @t  = @transfer_list;
  954.     
  955.             while( @t ){
  956.                 printf( "local_dir=%s remote_dir=%s patt=%s\n",
  957.                     shift( @t ), shift( @t ), shift( @t ) );
  958.             }
  959.         }
  960.     }
  961.     else {
  962.         push( @transfer_list, $local_dir );
  963.         push( @transfer_list, $remote_dir );
  964.         push( @transfer_list, $get_patt );
  965.         }
  966.         
  967.  
  968.     if( $update_local && $get_patt ){
  969.         if( $get_patt ne $default{ 'get_patt' } ){
  970.             &msg( "Cannot mix get_patt and update_local.  get_patt ignored\n" );
  971.         }
  972.         $get_patt = '';
  973.     }
  974.         
  975.     
  976.     if( !$site || (!$interactive && (!$local_dir || !$remote_dir)) ){
  977.         &msg( "Insufficient details for package to be fetched\n" );
  978.         &msg( "Must give at least: site, remote_user, remote_dir and local_dir\n\n" );
  979.         return $exit_status;
  980.     }
  981.  
  982.         if( $pretty_print ){
  983.                 # Don't actually mirror just print a pretty list
  984.                 # of what would be mirrored.  This is for mailing to
  985.                 # people
  986.         if( $skip ){
  987.             return $exit_ok;
  988.         }
  989.                 &msg( "$package  \"$comment\"\n" );
  990.                 &msg( "  $site:$remote_dir  -->  $local_dir\n\n" );
  991.                 return $exit_ok;
  992.         }
  993.  
  994.     if( $skip ){
  995.         &msg( "Skipping $site:$package because $skip\n\n" );
  996.         return $exit_ok;
  997.     }
  998.  
  999.     $split_max = &to_bytes( $split_max );
  1000.     $split_chunk = &to_bytes( $split_chunk );
  1001.  
  1002.     if( $split_max && $split_max <= $split_chunk ){
  1003.         &msg( "split_max <= split_chunk - skipping package\n" );
  1004.         &msg( " $split_max <= $split_chunk\n\n" );
  1005.         return $exit_status;
  1006.     }
  1007.  
  1008.     if( $split_chunk && ($split_chunk & 511) ){
  1009.         &msg( "split_chunk bad size - skipping package\n" );
  1010.         &msg( " $split_chunk should be a multiple of 512 bytes\n\n" );
  1011.         return $exit_status;
  1012.     }
  1013.  
  1014.     local( $con ) = &connect();
  1015.     if( $con <= 0 ){
  1016.         &msg( "Cannot connect, skipping package\n" );
  1017.         &disconnect();
  1018.         &msg( "\n" );
  1019.         return $exit_status;
  1020.     }
  1021.  
  1022.     if( $con == 1 ){
  1023.         &msg( "login as $remote_user\n" ) if $debug > 1;
  1024.         $curr_remote_user = $remote_user;
  1025.         if( ! &ftp'login( $remote_user, $remote_password ) ){
  1026.             &msg( "Cannot login, skipping package\n" );
  1027.             &disconnect();
  1028.             &msg( "\n" );
  1029.             return $exit_status;
  1030.         }
  1031.         $can_restart = (&ftp'restart(0) == 1);
  1032.         if( $debug > 1 ){
  1033.             &msg( "Can " . $can_restart ? '' : "not " . "do restarts\n" );
  1034.  
  1035.         }
  1036.     
  1037.         if( ! &ftp'type( $text_mode ? 'A' : 'I' ) ){
  1038.             &msg( "Cannot set type\n" );
  1039.         }
  1040.     }
  1041.     else {
  1042.         # Already connected to this site - so no need to login again
  1043.         &msg( "Already connected to site $site\n" ) if $debug;
  1044.     }
  1045.  
  1046.     $exit_status = $exit_fail; # ok this is now the worse case
  1047.  
  1048.     # Mirror thinks in terms of Unix pathnames.
  1049.     # Ask ftp.pl to map any remote name it is about to use by
  1050.     # setting the namemap functions.
  1051.     if( $remote_fs =~ /vms/i ){
  1052.         $vms = 1;
  1053.         &ftp'set_namemap( "main'unix2vms", "main'vms2unix" );
  1054.     }
  1055.     else {
  1056.         $vms = 0;
  1057.         # No mapping necessary
  1058.         &ftp'set_namemap( '' );
  1059.     }
  1060.  
  1061.     if( ! $get_file || $idle ){
  1062.         local( @rhelp ) = &ftp'site_commands();
  1063.         $remote_has_chmod = grep( $_ eq 'CHMOD', @rhelp);
  1064.         $remote_has_idle = grep( $_ eq 'IDLE', @rhelp);
  1065.         if( $debug > 2 ){
  1066.             &msg( "remote site " . ($remote_has_chmod ? "has" : "hasn't") . " got chmod\n" );
  1067.             &msg( "remote site " . ($remote_has_idle ? "has" : "hasn't") . " got idle\n" );
  1068.         }
  1069.     }
  1070.     
  1071.     if( $remote_has_idle && $remote_idle ){
  1072.         if( ! &ftp'quote( "site idle $remote_idle" ) ){
  1073.             &msg( "Cannot set remote idle\n" );
  1074.         }
  1075.         elsif( $debug > 2 ){
  1076.              &msg( "remote idle has been set to $remote_idle\n" );
  1077.         }
  1078.     }
  1079.  
  1080.     if( $remote_group ){
  1081.         if( ! &ftp'quote( "site group $remote_group" ) ){
  1082.             &msg( "Cannot set remote group\n" );
  1083.         }
  1084.         elsif( $debug > 2 ){
  1085.              &msg( "remote group has been set to $remote_group\n" );
  1086.         }
  1087.     }
  1088.     
  1089.     if( $remote_gpass ){
  1090.         if( ! &ftp'quote( "site gpass $remote_gpass" ) ){
  1091.             &msg( "Cannot set remote gpass\n" );
  1092.         }
  1093.         elsif( $debug > 2 ){
  1094.              &msg( "remote gpass has been set\n" );
  1095.         }
  1096.     }
  1097.  
  1098.     @log = ();
  1099.  
  1100.     while( @transfer_list ){
  1101.         # get files
  1102.         $local_dir = shift( @transfer_list );
  1103.         $remote_dir = shift( @transfer_list );
  1104.         $get_patt = shift( @transfer_list );
  1105.  
  1106.         # Clear all details
  1107.         undef( @xfer_dest, @xfer_src, @xfer_attribs, @things_to_make );
  1108.  
  1109.         if( $use_files ){
  1110.             &create_assocs();
  1111.         }
  1112.  
  1113.         if( !&get_local_directory_details() ){
  1114.             &msg( "Cannot get local directory details ($local_dir)\n" );
  1115.             &disconnect();
  1116.             &msg( "\n" );
  1117.             return $exit_status;
  1118.         }
  1119.  
  1120.         # Create a get_patt from the contents of the local directory
  1121.         if( $update_local && $#get_top >= 0 ){
  1122.             $get_patt = '^' . join( '|^', @get_top );
  1123.             $get_patt =~ s/$squished//g;
  1124.             &msg( "get_patt = $get_patt\n" ) if $debug;
  1125.         }
  1126.     
  1127.         if( !&get_remote_directory_details() ){
  1128.             &msg( "Cannot get remote directory details ($remote_dir)\n" );
  1129.             &disconnect();
  1130.             &msg( "\n" );
  1131.             return $exit_status;
  1132.         }
  1133.     
  1134.         if( $get_file ){
  1135.             &compare_dirs(
  1136.                 *remote_sorted,
  1137.                  *remote_map, *remote_time,
  1138.                   *remote_size, *remote_type,
  1139.                 *local_sorted,
  1140.                  *local_map, *local_time,
  1141.                   *local_size, *local_type,
  1142.                    *local_keep, *local_keep_totals );
  1143.         } else {
  1144.             &compare_dirs(
  1145.                 *local_sorted,
  1146.                  *local_map, *local_time,
  1147.                   *local_size, *local_type,
  1148.                 *remote_sorted,
  1149.                  *remote_map, *remote_time,
  1150.                   *remote_size, *remote_type,
  1151.                    *remote_keep, *remote_keep_totals );
  1152.         }
  1153.  
  1154.         if( $timestamp ){
  1155.             &set_timestamps();
  1156.             next;
  1157.         }
  1158.  
  1159.         &make_dirs();
  1160.         &do_all_transfers();
  1161.  
  1162.         $exit_status = $exit_ok;    # Everything went ok.
  1163.  
  1164.         if( $get_file ){
  1165.             # I must have finished with the remote information
  1166.             # so clear it out.
  1167.             &clear_remote();
  1168.         }
  1169.         else {
  1170.             # clear out local info.
  1171.             &clear_local();
  1172.         }
  1173.         
  1174.         if( $save_deletes ){
  1175.             # If $save_dir is null, make $save_dir to be
  1176.             # subdirectory 'Old' under 
  1177.             # current path
  1178.             if( ( ! defined( $save_dir ) ) || ( $save_dir eq '' ) ){
  1179.                 $save_dir = "$cwd/Old";
  1180.             }
  1181.  
  1182.             # If $save_dir is not absolute, take it as
  1183.             # subdirectory of current path
  1184.             if( $save_dir !~ m,^/, ){
  1185.                            $save_dir = "$cwd/$save_dir";
  1186.                    }
  1187.         }
  1188.  
  1189.         if( $do_deletes || $save_deletes ){
  1190.             if( $get_file ){
  1191.                 &do_deletes(
  1192.                     *local_sorted,
  1193.                      *local_map,
  1194.                       *local_type, *local_keep,
  1195.                        *local_totals, *local_keep_totals );
  1196.             }
  1197.             else {
  1198.                 &do_deletes(
  1199.                     *remote_sorted,
  1200.                      *remote_map,
  1201.                       *remote_type, *remote_keep,
  1202.                        *remote_totals, *remote_keep_totals );
  1203.             }
  1204.         }
  1205.  
  1206.         &make_symlinks();
  1207.         undef( @things_to_make );
  1208.  
  1209.         # No more transfers if the connection has died.
  1210.         last if ! $connected;
  1211.     }
  1212.  
  1213.     &clear_local();
  1214.     &clear_remote();
  1215.     
  1216.     if( $use_files ){
  1217.         # Close and zap.
  1218.         &delete_assocs();
  1219.     }
  1220.  
  1221.     # Should I force a disconnect now?
  1222.     if( $connected && $disconnect ){
  1223.         &disconnect();
  1224.     }
  1225.  
  1226.     if( $dont_do || $timestamp ){
  1227.         # Don't generate logs/email
  1228.         &msg( "\n" );
  1229.         return $exit_status;
  1230.     }
  1231.  
  1232.     local( $now );
  1233.     chop( $now = `date` );
  1234.     if( $update_log ){
  1235.         if( ! open( logg, ">>$update_log" ) ){
  1236.             &msg( "Cannot append to $update_log\n\n" );
  1237.             return $exit_fail;
  1238.         }
  1239.         print logg "mirroring $package ($site:$remote_dir) completed successfully @ $now\n";
  1240.         print logg @log;
  1241.         close( logg );
  1242.     }
  1243.  
  1244.     if( $#log >= 0 && $mail_to =~ /./ ){
  1245.         local( $arg );
  1246.         eval "\$arg = \"$mail_subject\"";
  1247.         if( ! open( mail, "|$mail_prog $arg $mail_to" ) ){
  1248.             &msg( "Cannot run: $com\n\n" );
  1249.             return $exit_fail;
  1250.         }
  1251.         print mail "Mirrored $package ($site:$remote_dir -> $local_dir) $comment @ $now\n";
  1252.         print mail @log;
  1253.         close( mail );
  1254.     }
  1255.     undef( @log );
  1256.  
  1257.     &msg( "\n" );
  1258.     return $exit_status;
  1259. }
  1260.  
  1261.  
  1262. sub disconnect
  1263. {
  1264.     if( $connected ){
  1265.         &msg( "disconnecting from $connected\n" ) if $debug;
  1266.         if( ! $ftp'fatalerror ){
  1267.             &ftp'quit();
  1268.         }
  1269.     }
  1270.     $connected = '';
  1271. }
  1272.  
  1273. # Connect to the site
  1274. # Return 0 on a fail,
  1275. # 1 if a connection was successfully made,
  1276. # 2 if already connected to the site
  1277. sub connect
  1278. {
  1279.     local( $attempts ) = 1; # Retry ONCE! Be friendly.
  1280.     local( $res );
  1281.  
  1282.     if( $connected eq $site && $curr_remote_user eq $remote_user ){
  1283.         # Already connected to this site!
  1284.         return 2;
  1285.     }
  1286.  
  1287.     # Clear out any session active session
  1288.     &disconnect();
  1289.  
  1290.     if( $proxy ){
  1291.         $ftp'proxy = $proxy;
  1292.         $ftp'proxy_gateway = $proxy_gateway;
  1293.         $ftp'proxy_ftp_port = $proxy_ftp_port;
  1294.         $ftp'site = $site;
  1295.     }
  1296.     $res = &ftp'open( $site, $ftp_port, $retry_call, $attempts );
  1297.     if( $res == 1 ){
  1298.         # Connected
  1299.         $connected = $site;
  1300.     }
  1301.     return $res;
  1302. }    
  1303.  
  1304. # This just prods the remote ftpd to prevent time-outs
  1305. sub prod
  1306. {
  1307.     if( $debug > 2 ){
  1308.         &msg( " prodding remote ftpd\n" );
  1309.     }
  1310.     &ftp'pwd();
  1311. }
  1312.  
  1313. # checkout and fixup any regexps.
  1314. # return 0 on an error
  1315. sub checkout_regexps
  1316. {
  1317.     local( $ret ) = 1;
  1318.     # Check out the regexps
  1319.     local( $t ) = 'x';
  1320.     foreach $r ( @regexp_values ){
  1321.         # regexps should never begin or end with a | or have
  1322.         # two in a row otherwise the pattern matches everything.
  1323.         # Use null to match everything if thats what you mean.
  1324.         $value{ $r } =~ s/\|+/|/g;
  1325.         $value{ $r } =~ s/^\|//;
  1326.         $value{ $r } =~ s/\|$//;
  1327.         local( $val ) = $value{ $r };
  1328.         next if ! $val;
  1329.         eval '$t =~ /$val/';
  1330.         if( $@ ){
  1331.             local( $err );
  1332.             chop( $err = $@ );
  1333.             &msg( "Problem with regexp $r ($err)\n" );
  1334.             $ret = 0;
  1335.         }
  1336.     }
  1337.     return $ret;
  1338. }
  1339.  
  1340. sub clear_local
  1341. {
  1342.     undef( @local_sorted );
  1343.     if( ! $use_files ){
  1344.         undef( %local_map );
  1345.     }
  1346.     undef( @local_time, @local_size,
  1347.         @local_type, @local_mode,
  1348.         @local_keep, @local_totals, @local_keep_totals );
  1349. }
  1350.  
  1351. sub clear_remote
  1352. {
  1353.     undef( @remote_sorted );
  1354.     if( ! $use_files ){
  1355.         undef( %remote_map );
  1356.     }
  1357.     undef( @remote_time, @remote_size,
  1358.         @remote_type, @remote_mode,
  1359.         @remote_keep, @remote_totals, @remote_keep_totals );
  1360. }
  1361.  
  1362. sub get_local_directory_details
  1363. {
  1364.     local( @dirs, $dir );
  1365.     local( $last_prodded ) = time; # when I last prodded the remote ftpd
  1366.  
  1367.     $next_local_mapi = $map_init;
  1368.     
  1369.     &clear_local();
  1370.     
  1371.     # Make sure the first elem is 0.
  1372.     push( @local_time, 0 );
  1373.     push( @local_size, 0 );
  1374.     push( @local_type, 0 );
  1375.     push( @local_mode, 0 );
  1376.  
  1377.     @get_top = ();
  1378.  
  1379.     &msg( "Scanning local directory $local_dir\n" ) if $debug;
  1380.     
  1381.     if( ! -d $local_dir ){
  1382.         if( $dont_do || $timestamp ){
  1383.             return 1;
  1384.         }
  1385.         &msg( "$local_dir no such directory - creating it\n" );
  1386.         if( &mkdirs( $local_dir ) ){
  1387.             push( @log, "Created dir $local_dir\n" );
  1388.             $exit_xfer_status |= $exit_xfers;
  1389.         }
  1390.         else {
  1391.             &msg( $log, "FAILED to create local dir $local_dir\n" );
  1392.         }
  1393.     }
  1394.     if( !chdir( $local_dir ) ){
  1395.         &msg( "Cannot change directory to $local_dir\n" );
  1396.         return 0;
  1397.     }
  1398.  
  1399.     if( $local_dir =~ m,^/, ){
  1400.         $cwd = $local_dir;
  1401.     }
  1402.     else {
  1403.         chop( $cwd = `pwd` );
  1404.     }
  1405.  
  1406.     # @dirs is the list of all directories to scan
  1407.     # As subdirs are found they are added to the end of the list
  1408.     # and as 
  1409.     @dirs = ( "." );
  1410.     # Most of these variables should be locals in blocks below but
  1411.     # that seems to tickle a perl bug and causes a lot of memory to
  1412.     # be wasted.
  1413.     local( $dir_level ) = 0;
  1414.     local( $i ) = 0;
  1415.     local( $path, $time, $size, $type, $mode, $name, $isdir, $value, $follow );
  1416.     local( $dev,$ino,$fmode,$nlink,$uid,$gid,$rdev,$ssize,
  1417.               $atime,$mtime,$ctime,$blksize,$blocks );
  1418.     while( defined( $dir = shift( @dirs ) ) ){
  1419.  
  1420.         if( !opendir( dir, $dir ) ){
  1421.             &msg( "Cannot open local directory $dir, skipping it\n" );
  1422.             next;
  1423.         }
  1424.  
  1425.         while( defined( $name = readdir( dir ) ) ){
  1426.             $isdir = 0;
  1427.  
  1428.             # Prod the remote system from time to time
  1429.             # To prevent time outs.  Only look once every 50 files
  1430.             # to save on unnecessary systems calls.
  1431.             if( ($i % 50 == 0) && time > ($last_prodded + $prod_interval) ){
  1432.                 $last_prodded = time;
  1433.                 &prod();
  1434.             }
  1435.             $i ++;
  1436.  
  1437.             $path = "$dir/$name";
  1438.             $path =~ s,(^|/)\./,,;
  1439.             next if $name eq '.' || $name eq '..' ||
  1440.                 ($local_ignore && $path =~ /$local_ignore/);
  1441.  
  1442.             $follow = ($follow_local_symlinks ne '' && $path =~ /$follow_local_symlinks/);
  1443.             if( !$follow && -l $path ){
  1444.                 $value = readlink( $path );
  1445.                 ( $dev,$ino,$fmode,$nlink,$uid,$gid,$rdev,$ssize,
  1446.                       $atime,$mtime,$ctime,$blksize,$blocks ) =
  1447.                     lstat( _ );
  1448.                 $size = $ssize;
  1449.                 $time = $mtime;
  1450.                 $type = "l $value";
  1451.                 $mode = $fmode;
  1452.             }
  1453.             elsif( ($isdir = ($follow ? (-d $path) : (-d _))) ||
  1454.                      -f _ ){
  1455.                 ( $dev,$ino,$fmode,$nlink,$uid,$gid,$rdev,$ssize,
  1456.                       $atime,$mtime,$ctime,$blksize,$blocks ) =
  1457.                     stat( _ );
  1458.                 $size = $ssize;
  1459.                 $time = $mtime;
  1460.                 $mode = $fmode;
  1461.                 if( $isdir ){
  1462.                     push( @dirs, $path ) if $recursive;
  1463.                     $type = 'd';
  1464.                 }
  1465.                 else {
  1466.                     $type = 'f';
  1467.                 }
  1468.                 if( $dir_level == 0 && $update_local ){
  1469.                     push( @get_top, $path );
  1470.                 }
  1471.             }
  1472.             else {
  1473.                 &msg( "unknown file type $path, skipping\n" );
  1474.                 next;
  1475.             }
  1476.             if( $debug > 2){
  1477.                 printf "local: %s %s %s %s 0%o\n",
  1478.                     $path, $size, $time, $type, $mode;
  1479.             }
  1480.             if( $max_age && $time != 0 && $time < $max_age ){
  1481.                 &msg( "   too old: $path\n" ) if $debug > 1;
  1482.                 next;
  1483.             }
  1484.  
  1485.             push( @local_sorted, $path );
  1486.             local( $mapi ) = $next_local_mapi++;
  1487.             $local_map{ $path } = $mapi;
  1488.             push( @local_time, $time );
  1489.             push( @local_size, $size );
  1490.             push( @local_type, $type );
  1491.             push( @local_mode, $mode );
  1492.             if( $type eq 'd' ){
  1493.                 $local_totals[ 0 ]++;
  1494.             }
  1495.             else {
  1496.                 $local_totals[ 1 ]++;
  1497.             }
  1498.         }
  1499.         closedir( dir );
  1500.         $dir_level++;
  1501.  
  1502.         if( ! $recursive ){
  1503.             last;
  1504.         }
  1505.     }
  1506.     return 1;
  1507. }
  1508.  
  1509. # Return true if the remote directory listing was brought back safely.
  1510. sub get_remote_directory_details
  1511. {
  1512.     local( $use_rls ) = 0;
  1513.     local( $type_changed ) = 0;
  1514.     local( $udirtmp );
  1515.  
  1516.     &msg( "Scanning remote directory $remote_dir\n" ) if $debug;
  1517.     
  1518.     $next_remote_mapi = $map_init;
  1519.     &clear_remote();
  1520.  
  1521.     # Make sure the first elem is 0.
  1522.     push( @remote_time, 0 );
  1523.     push( @remote_size, 0 );
  1524.     push( @remote_type, 0 );
  1525.     push( @remote_mode, 0 );
  1526.  
  1527.     if( ! &ftp'cwd( $remote_dir ) ){
  1528.         if( $get_file ){
  1529.             # no files to get
  1530.             return 0;
  1531.         }
  1532.  
  1533.         &msg( "Failed to change to remote directory ($remote_dir) trying to create it\n" );
  1534.         &mkdirs( $remote_dir );
  1535.  
  1536.         if( ! &ftp'cwd( $remote_dir ) ){
  1537.             &msg( "Cannot change to remote directory ($remote_dir) because: $ftp'response\n" );
  1538.             return 0;
  1539.         }
  1540.     }
  1541.  
  1542.     local( $rls );
  1543.  
  1544.     if( $local_ls_lR_file ){
  1545.         &msg( " Using local file $local_ls_lR_file for remote dir listing\n" ) if $debug;
  1546.         if( ! open( dirtmp, $local_ls_lR_file ) ){
  1547.             &msg( "Cannot open $local_ls_lR_file\n" );
  1548.             return 0;
  1549.         }
  1550.         $rls = "main'dirtmp";
  1551.     }
  1552.     elsif( $ls_lR_file ){
  1553.         local( $dirtmp );
  1554.         $dirtmp = "$big_temp/.dir$$";
  1555.         if( $ls_lR_file =~ /\.($sys_compress_suffix|$gzip_suffix|$old_gzip_suffix)$/ ){
  1556.             $dirtmp .= ".$1";
  1557.         }
  1558.  
  1559.         &msg( " Getting directory listing from remote file $ls_lR_file\n" ) if $debug;
  1560.         if( ! &ftp'get( $ls_lR_file, $dirtmp, 0 ) ){
  1561.             &msg( "Cannot get dir listing file\n" );
  1562.             return 0;
  1563.         }
  1564.         local( $unsquish );
  1565.         if( $dirtmp =~ /\.$sys_compress_suffix$/ ){
  1566.             $unsquish = $sys_compress_prog;
  1567.         }
  1568.         elsif( $dirtmp =~ /\.($gzip_suffix|$old_gzip_suffix)$/ ){
  1569.             $unsquish = $gzip_prog;
  1570.         }
  1571.           if( defined( $unsquish ) ){
  1572.               local( $f, $uf );
  1573.             $uf = $udirtmp = $dirtmp;
  1574.             $dirtmp =~ s/($shell_metachars)/\\$1/g;
  1575.               $f = $dirtmp;
  1576.               $dirtmp =~ s/\.($sys_compress_suffix|$gzip_suffix|$old_gzip_suffix)$//;
  1577.               $udirtmp =~ s/\.($sys_compress_suffix|$gzip_suffix|$old_gzip_suffix)$//;
  1578.             &sys( "$unsquish -d < \"$f\" > \"$dirtmp\"" );
  1579.               unlink( $uf );
  1580.           }
  1581.  
  1582.         open( dirtmp, $dirtmp ) || die "Cannot open $dirtmp";
  1583.         $rls = "main'dirtmp";
  1584.     }
  1585.     else {
  1586.         $use_ls = 1;
  1587.         if( ! &ftp'type( 'A' ) ){
  1588.             &msg( "Cannot set type to ascii for dir listing, ignored\n" );
  1589.             $type_changed = 0;
  1590.         }
  1591.         else {
  1592.             $type_changed = 1;
  1593.         }
  1594.     }
  1595.     
  1596.     $lsparse'fstype = $remote_fs;
  1597.     $lsparse'name = "$site:$package";
  1598.     
  1599.     if( $use_ls ){
  1600.          if( !&ftp'dir_open( $recursive ? $flags_recursive : $flags_nonrecursive ) ){
  1601.             &msg( "Cannot get remote directory listing because: $ftp'response\n" );
  1602.             return 0;
  1603.         }
  1604.         
  1605.         $rls = "ftp'NS";
  1606.     }
  1607.         
  1608.     $rcwd = '';
  1609.     if( $vms ){
  1610.         # Strip this off all pathnames to make them
  1611.         # relative to the remote_dir
  1612.         $rcwd = $remote_dir;
  1613.     }
  1614.     $dateconv'use_timelocal = $use_timelocal;
  1615.     if( !&lsparse'reset( $rcwd ) ){
  1616.         &msg( "$remote_fs: unknown fstype\n" );
  1617.         return 0;
  1618.     }
  1619.     if( $vms ){
  1620.         # Need to get in terms of the full pathname
  1621.         # so add it back in - see unix2vms at end of mirror
  1622.         $vms_dir = $remote_dir;
  1623.     }
  1624.     
  1625.     local( $parse_state ) = &parse_remote_details();
  1626.     
  1627.     if( $local_ls_lR_file ){
  1628.         close( dirtmp );
  1629.     }
  1630.     elsif( $ls_lR_file ){
  1631.         close( dirtmp );
  1632.         unlink( $udirtmp );
  1633.     }
  1634.     else {
  1635.         # Could optimise this out - but it makes sure that
  1636.         # the other end gets a command straight after a possibly
  1637.         # long dir listing.
  1638.         if( ! &ftp'type( $text_mode ? 'A' : 'I' ) ){
  1639.             local( $msg ) = "Cannot reset type after dir listing, ";
  1640.             if( $type_changed ){
  1641.                 # I changed it before - so I must be able to
  1642.                 # change back unless something is wrong
  1643.                 $msg .= "aborting\n";
  1644.                 &msg( $msg );
  1645.                 return 0;
  1646.             }
  1647.             else {
  1648.                 $msg .= "ignoring\n";
  1649.                 &msg( $msg );
  1650.             }
  1651.         }
  1652.     }
  1653.  
  1654.     # If the other end dropped part way thru make sure the
  1655.     # higher routines know!
  1656.     return $parse_state;
  1657. }
  1658.  
  1659. sub parse_timeout
  1660. {
  1661.     $parse_timed_out = 1;
  1662.     die "timeout: parse_remote_details";
  1663. }
  1664.  
  1665. sub parse_remote_details
  1666. {
  1667.     local( $ret );
  1668.  
  1669.     $parse_timed_out = 0;
  1670.     
  1671.     if( ! $use_ls ){
  1672.         # No need to bother with the timers
  1673.         return &parse_remote_details_real();
  1674.     }
  1675.     
  1676.     # This may timeout
  1677.     $SIG{ 'ALRM' } = "main\'parse_timeout";
  1678.     
  1679.     $ret = eval '&parse_remote_details_real()';
  1680.     
  1681.     alarm( 0 );
  1682.  
  1683.     if( $@ =~ /^timeout/ ){
  1684.         &msg( "timed out parsing directory details\n" );
  1685.         return 0;
  1686.     }
  1687.     return $ret;
  1688. }
  1689.  
  1690.  
  1691. sub parse_remote_details_real
  1692. {
  1693.     local( $path, $size, $time, $type, $mode, $rdir, $rcwd );
  1694.     local( @dir_list );
  1695.     local( $i ) = 0;
  1696.     
  1697.     if( $use_ls ){
  1698.         alarm( $parse_time );
  1699.     }
  1700.     
  1701.     # Need to loop in case $recurse_hard
  1702.     while( 1 ){
  1703.         while( !eof( $rls ) ){
  1704.             ( $path, $size, $time, $type, $mode ) =
  1705.                 &lsparse'line( $rls );
  1706.             last if !$path;
  1707.             if( $debug > 2 ){
  1708.                 printf "remote: %s %s %s %s 0%o\n",
  1709.                      $path, $size, $time, $type, $mode;
  1710.             }
  1711.             if( $use_ls ){
  1712.                 # I just got something so shouldn't timeout
  1713.                 alarm( $parse_time );
  1714.             }
  1715.             else {
  1716.                 # Prod the remote system from time to time
  1717.                 # To prevent time outs.  Only look once every
  1718.                 # 50 files
  1719.                 # to save on unnecessary systems calls.
  1720.                 if( ($i % 50 == 0) &&
  1721.                     time > ($last_prodded + $prod_interval) ){
  1722.                     $last_prodded = time;
  1723.                     &prod();
  1724.                 }
  1725.                 $i ++;
  1726.             }
  1727.             
  1728.             
  1729.             if( $type eq 'd' && $recurse_hard ){
  1730.                 push( @dir_list, $path );
  1731.             }
  1732.             
  1733.             if( $max_age && $time != 0 && $time < $max_age ){
  1734.                 &msg( "   too old: $path\n" ) if $debug > 1;
  1735.                 next;
  1736.             }
  1737.             
  1738.             if( $exclude_patt && $path =~ /$exclude_patt/ ){
  1739.                 &msg( "   exclude: $path\n" ) if $debug > 1;
  1740.                 next;
  1741.             }
  1742.  
  1743.             # If vms and only keeping the latest version
  1744.             if( $vms && !$vms_keep_versions ){
  1745.                 # If we already have a file, pick the newer
  1746.                 # TODO: pick the greatest version number
  1747.                 local( $ri ) = $remote_map{ $path };
  1748.                 if( $ri &&
  1749.                     $time > $remote_time[ $ri ] ){
  1750.                     $remote_time[ $ri ] = $time;
  1751.                     $remote_size[ $ri ] = $size;
  1752.                     $remote_type[ $ri ] = $type;
  1753.                     $remote_mode[ $ri ] = $mode;
  1754.                     next;
  1755.                 }
  1756.             }
  1757.             
  1758.             push( @remote_sorted, $path );
  1759.             local( $mapi ) = $next_remote_mapi++;
  1760.             $remote_map{ $path } = $mapi;
  1761.             push( @remote_time, $time );
  1762.             push( @remote_size, $size );
  1763.             push( @remote_type, $type );
  1764.             push( @remote_mode, $mode );
  1765.             if( $type eq 'd' ){
  1766.                 $remote_totals[ 0 ]++;
  1767.             }
  1768.             else {
  1769.                 $remote_totals[ 1 ]++;
  1770.             }
  1771.         }
  1772.  
  1773.         if( $use_ls ){
  1774.             &ftp'dir_close();
  1775.         }
  1776.         
  1777.         if( $recurse_hard && $#dir_list < 0 ){
  1778.             # Make sure we end in the right directory.
  1779.             if( ! &ftp'cwd( $remote_dir ) ){
  1780.                 &msg( "Cannot change to remote directory" .
  1781.                  " ($rdir) because: $ftp'response\n" );
  1782.                 return 0;
  1783.             }
  1784.         }
  1785.         elsif( $recurse_hard ){
  1786.             $rcwd = shift( @dir_list );
  1787.             $rdir = "$remote_dir/$rcwd";
  1788.             if( $debug > 2 ){
  1789.                 print "scanning: $remote_dir / $rcwd\n";
  1790.             }
  1791.             if( ! &ftp'cwd( $rdir ) ){
  1792.                 &msg( "Cannot change to remote directory" .
  1793.                  " ($rdir) because: $ftp'response\n" );
  1794.                 return 0;
  1795.             }
  1796.              if( !&ftp'dir_open( $recursive ? $flags_recursive : $flags_nonrecursive ) ){
  1797.                 &msg( "Cannot get remote directory" .
  1798.                       " listing because: $ftp'response\n" );
  1799.                 return 0;
  1800.             }
  1801.             &lsparse'reset( $rcwd );
  1802.             
  1803.             # round the loop again.
  1804.             next;
  1805.         }
  1806.         
  1807.         # All done - snap the loop
  1808.         last;
  1809.     }
  1810.     return 1;
  1811. }
  1812.  
  1813. sub compare_dirs
  1814. {
  1815.     local( *src_paths,
  1816.         *src_map, *src_time,
  1817.          *src_size, *src_type, 
  1818.            *dest_paths,
  1819.         *dest_map, *dest_time,
  1820.          *dest_size, *dest_type,
  1821.           *dest_keep, *dest_keep_totals ) = @_;
  1822.     local( $src_path, $dest_path, $dest_index, $i );
  1823.     local( $last_prodded ) = time; # when I last prodded the remote ftpd
  1824.     local( $i );
  1825.  
  1826.     # Most of these variables should be locals in blocks below but
  1827.     # that seems to tickle a perl bug and causes a lot of memory to
  1828.     # be wasted.
  1829.     local( $desti, $srci, $compress, $srciZ, $srcigz, $split, $dest_path_real );
  1830.     local( $old_dest_path, $existing_path, $tmp, $tempi, $restart );
  1831.     local( $sp, $dp ) = ($#src_paths + 1, $#dest_paths + 1);
  1832.     
  1833.     &msg( "compare directories (src $sp, dest $dp)\n" ) if $debug;
  1834.  
  1835.     for( $i = 0; $i <= $#src_paths; $i++ ){
  1836.         $dest_path = $src_path = $src_paths[ $i ];
  1837.         
  1838.         $desti = $dest_map{ $dest_path };
  1839.         $srci = $i + 1;
  1840.  
  1841.         # Prod the remote system from time to time
  1842.         # To prevent time outs.  Only look once every 50 files
  1843.         # to save on unnecessary systems calls.
  1844.         if( ($i % 50 == 0) && time > ($last_prodded + $prod_interval) ){
  1845.             $last_prodded = time;
  1846.             &prod();
  1847.         }
  1848.  
  1849.         if( $debug > 2 ){
  1850.             &msg( "Compare src $src_path ($srci): " .
  1851.                 &t2str( $src_time[ $srci ] ) );
  1852.             &msg( " $src_size[ $srci ] $src_type[ $srci ]\n" );
  1853.         }
  1854.  
  1855.         # I'm about to do a lot of matching on this
  1856.         study( $src_path );
  1857.  
  1858.         # Should I compress this file?
  1859.         #  Don't compress this file if trying to do a compress->gzip
  1860.         # conversion.
  1861.         $compress = 0;
  1862.         if( &will_compress( $src_path, $srci ) ){
  1863.             if( $dest_path !~ /$squished/o ){
  1864.                 $srciZ = $src_map{ "$src_path.$sys_compress_suffix" };
  1865.                 $srcigz = $src_map{ "$src_path.$gzip_suffix" };
  1866.                 if( $srciZ || $srcigz ){
  1867.                     # There is a compressed version
  1868.                     # too!  Skip the uncompressed one
  1869.                     &msg( "   do not xfer, compressed version exists: $src_path\n" ) if $debug > 1;
  1870.                     next;
  1871.                 }
  1872.  
  1873.                 $compress = 1;
  1874.                 $dest_path .= '.' . $compress_suffix;
  1875.                 $desti = $dest_map{ $dest_path };
  1876.             }
  1877.         }
  1878.         # need to adjust the symlink pointer?
  1879.         elsif( $src_type[ $srci ] =~ /^l (.*)/ ){
  1880.            # Am I going to squish the file this points to?
  1881.            local( $real ) = &expand_symlink( $src_path, $1 );
  1882.            local( $reali ) = $src_map{ $real };
  1883.            if( &will_compress( $real, $reali ) ){
  1884.             # real is going to be (at least) squished so
  1885.             # suffix the dest
  1886.             $dest_path .= '.' . $compress_suffix;
  1887.             $desti = $dest_map{ $dest_path };
  1888.             $src_type[ $srci ] .= '.' . $compress_suffix;
  1889.             &msg( "  symlink pointer is now $dest_path\n" ) if $debug > 1;
  1890.             if( $src_map{ $dest_path } ){
  1891.                 &msg( "do not xfer, $dest_path exists\n" ) if $debug > 1;
  1892.                 next;
  1893.             }
  1894.            }
  1895.            if( &will_split( $real, $reali ) ){
  1896.             $src_type[ $srci ] .= '-split/README';
  1897.             &msg( "  symlink pointer now to $real-split/README'\n" ) if $debug > 1;
  1898.            }
  1899.         }
  1900.         
  1901.         # If this is a file that I decided not to compress but the
  1902.         # remote file is compressed and I want a gziped local version
  1903.         # then force compression.
  1904.         # This ignores any compress_excl flags.
  1905.         if( ! $compress &&
  1906.             $compress_suffix eq $gzip_suffix &&
  1907.             $compress_conv_patt && $src_path =~ /$compress_conv_patt/ ){
  1908.             $_ = $dest_path;
  1909.             eval $compress_conv_expr;
  1910.             $dest_path = $_;
  1911.             &msg( "   $src_path -> $dest_path\n" ) if $debug > 2;
  1912.             $desti = $dest_map{ $dest_path };
  1913.             $compress = 1;
  1914.         }
  1915.  
  1916.         # Am I converting the compression on the file this points to?
  1917.         if( $src_type[ $srci ] =~ /^l (.*)/ &&
  1918.               $compress_suffix eq $gzip_suffix ){
  1919.                 local( $value ) = $1;
  1920.             local( $real ) = &expand_symlink( $src_path, $1 );
  1921.                 local( $reali ) = $src_map{ $real };
  1922.                 if( $src_type[ $reali ] ne 'd' &&
  1923.                 $src_type[ $reali ] ne /^l .*/ &&
  1924.                 $compress_conv_patt && $real =~ /$compress_conv_patt/ ){
  1925.                 $dest_path =~ s/$sys_compress_suffix$/$gzip_suffix/;
  1926.                 $desti = $dest_map{ $dest_path };
  1927.                 $value =~ s/$sys_compress_suffix$/$gzip_suffix/;
  1928.                 &msg( "  symlink pointer is now $dest_path (conv)\n")
  1929.                  if $debug > 1;
  1930.             }
  1931.             if( $name_mappings || $external_mapping ){
  1932.                     local( $old ) = $value;
  1933.                 $value = &map_name( $value );
  1934.                 if( $value ne $old ){
  1935.                     &msg( "   Mapped symlink value is $value\n" ) if $debug > 2;
  1936.                 }
  1937.                     
  1938.             }
  1939.             $src_type[ $srci ] = "l ".$value;
  1940.         }
  1941.  
  1942.         if( $name_mappings || $external_mapping ){
  1943.             local( $old_dest_path ) = $dest_path;
  1944.             $dest_path = &map_name( $dest_path );
  1945.             if( $dest_path ne $old_dest_path ){
  1946.                 $desti = $dest_map{ $dest_path };
  1947.                 &msg( "   Mapped name is $dest_path\n" ) if $debug > 2;
  1948.             }
  1949.         }
  1950.         
  1951.         # Should this file be split?
  1952.         $split = 0;
  1953.         $dest_path_real = undef;
  1954.         if( &will_split( $src_path, $srci ) ){
  1955.             $split = 1;
  1956.             $dest_path_real = $dest_path;
  1957.             $dest_path .= "-split/part01";
  1958.             $desti = $dest_map{ $dest_path };
  1959.         }
  1960.  
  1961.         if( $debug > 2 ){
  1962.             &msg( "       dest $dest_path ($desti): " .
  1963.                 &t2str( $dest_time[ $desti ] ) );
  1964.             &msg( " $dest_size[ $desti ] $dest_type[ $desti ]" );
  1965.             &msg( " (->$compress_suffix)" ) if $compress;
  1966.             &msg( " (split)" ) if $split;
  1967.             &msg( "\n" );
  1968.         }
  1969.         
  1970.         if( $get_patt && $src_path !~ /$get_patt/ ){
  1971.             &msg( "   do not xfer: $src_path\n" ) if $debug > 1;
  1972.             next;
  1973.         }
  1974.  
  1975.         # Just create any needed directories (the timestamps
  1976.         # should be ignored)
  1977.         if( $src_type[ $srci ] eq 'd' ){
  1978.             if( $dest_type[ $desti ] ne 'd' ){
  1979.                 push( @things_to_make, "d $dest_path" );
  1980.                 &msg( "   need to mkdir $dest_path\n" ) if $debug > 1;
  1981.             }
  1982.             # keep the directory once made
  1983.             # (Also if local is really a symlink elsewhere
  1984.             #  it will be kept.)
  1985.             $dest_keep[ $desti ] = 1;
  1986.             $dest_keep_totals[ 0 ]++;
  1987.             &msg( "   keep $dest_path\n" ) if $debug > 2;
  1988.             next;
  1989.         }
  1990.  
  1991.         # Well that just leaves files and symlinks.
  1992.         # Do various checks on them.
  1993.  
  1994.         if( $desti && ! $dest_keep[ $desti ] ){
  1995.             $dest_keep[ $desti ] = 1;
  1996.             $dest_keep_totals[ 1 ]++;
  1997.             &msg( "   keep $dest_path\n" ) if $debug > 2;
  1998.             if( $split ){
  1999.                 # Mark all the split parts as kept
  2000.                 local( $dpp, $dps, $dpi );
  2001.                 ($dpp, $dps) = ($dest_path =~ m,^(.*/)(part[0-9]+)$,);
  2002.                 while( 1 ){
  2003.                     $dps++;
  2004.                     if( !($di = $dest_map{ $dpp . $dps }) ){
  2005.                         last;
  2006.                     }
  2007.                     $dest_keep[ $di ] = 1;
  2008.                     $dest_keep_totals[ 1 ]++;
  2009.                     &msg( "      keep $dpp$dps\n" ) if $debug > 2;
  2010.                 }
  2011.                 # And the README
  2012.                 $dps = 'README';
  2013.                 $di = $dest_map{ $dpp . $dps };
  2014.                 if( $di ){
  2015.                     $dest_keep[ $di ] = 1;
  2016.                     $dest_keep_totals[ 1 ]++;
  2017.                     &msg( "      keep $dpp$dps\n" ) if $debug > 2;
  2018.                 }
  2019.                 # And the directory
  2020.                 chop( $dpp );
  2021.                 $dps = '';
  2022.                 $di = $dest_map{ $dpp . $dps };
  2023.                 if( $di ){
  2024.                     $dest_keep[ $di ] = 1;
  2025.                     $dest_keep_totals[ 0 ]++;
  2026.                     &msg( "      keep $dpp$dps\n" ) if $debug > 2;
  2027.                 }
  2028.             }
  2029.         }
  2030.         
  2031.         local( $update ) = 0;
  2032.  
  2033.         if( ! $get_missing ){
  2034.             next;
  2035.         }
  2036.  
  2037.         if( $force || ! $dest_type[ $desti ] || $timestamp ){
  2038.             # Either I'm forcing xfers or the file doesn't exist
  2039.             # either way I should update
  2040.             $update = 1;
  2041.         }
  2042.         else {
  2043.             # Maybe the src is newer?
  2044.             if( $get_newer &&
  2045.                &compare_times( $src_time[ $srci ], $dest_time[ $desti ] ) ){
  2046.                 &msg( "   src is newer, xfer it\n" ) if $debug > 2;
  2047.                 $update = 1;
  2048.             }
  2049.             # or maybe its size has changed?
  2050.             # don't bother if file was compressed or split as the
  2051.             # size will have changed anyway
  2052.             if( !$compress && !$split &&
  2053.                $get_size_change &&
  2054.                ($src_type[ $srci ] eq 'f') &&
  2055.                ($src_size[ $srci ] != $dest_size[ $desti ]) ){
  2056.                 $update = 1;
  2057.             }
  2058.             # Maybe it has changed type!
  2059.             if( $src_type[ $srci ] ne $dest_type[ $desti ] ){
  2060.                 $update = 1;
  2061.             }
  2062.             if( $update && $debug > 2 ){
  2063.                 &msg( "   src is different size, xfer it\n" );
  2064.             }
  2065.         }
  2066.  
  2067.         if( ! $update ){
  2068.             next;
  2069.         }
  2070.  
  2071.         if( $src_type[ $srci ] =~ /^l (.*)/ ){
  2072.             # If the symlink hasn't changed then may as well 
  2073.             # leave it alone
  2074.             if( $src_type[ $srci ] eq $dest_type[ $desti ] ){
  2075.                 next;
  2076.             }
  2077.             # DONT FORGET TO NAME MAP!!!!
  2078.             $existing_path = $1;
  2079.  
  2080.             if( $compress_suffix eq $gzip_suffix &&
  2081.                 $compress_conv_patt && $existing_path =~ /$compress_conv_patt/ ){
  2082.                 $_ = $existing_path;
  2083.                 eval $compress_conv_expr;
  2084.                 $existing_path = $_;
  2085.             }
  2086.  
  2087.             push( @things_to_make, "l $dest_path -> $existing_path" );
  2088.             &msg( "   need to symlink $dest_path -> $existing_path\n" ) if $debug > 2;
  2089.             next;
  2090.         }
  2091.  
  2092.         # Now that the tests are complete use the real dest.
  2093.         if( defined( $dest_path_real ) ){
  2094.             $dest_path = $dest_path_real;
  2095.             $desti = $dest_map{ $dest_path };
  2096.         }
  2097.  
  2098.         &msg( "$XFER file $src_path as $dest_path".
  2099.             ($compress ? " (->$compress_suffix)" : "") .
  2100.             ($split ? " (split)" : "") . "\n" ) if $debug > 1;
  2101.         push( @xfer_dest, $dest_path );
  2102.         push( @xfer_src, $src_path );
  2103.  
  2104.         # If xfers can be restarted AND
  2105.         # a temporary file exists from a previous attempt at a
  2106.         # transfer  AND
  2107.         # the timestamps of the exising temp file and the original
  2108.         # src file match then flag a restart.
  2109.         $tmp = &filename_to_tempname( '', $dest_path );
  2110.         $tmpi = $dest_map{ $tmp };
  2111.         $restart = '';
  2112.         if( $get_file &&
  2113.            $can_restart &&
  2114.            -f $tmp &&
  2115.            ($dest_time[ $tmpi ] eq $src_time[ $srci ]) ){
  2116.             # Then this is an xfer of the same file
  2117.             # so just restart where I left off
  2118.             $restart = 'r';
  2119.         }
  2120.         # x for xfer, c for compress, s for split
  2121.         push( @xfer_attribs,
  2122.              "x$restart" .
  2123.              ($compress ? "c" : "") .
  2124.              ($split ? "s" : "") );
  2125.     }
  2126. }
  2127.  
  2128. sub map_name
  2129. {
  2130.     local( $name ) = @_;
  2131.  
  2132.     if( $name_mappings ){
  2133.         local( $old_name ) = $name;
  2134.         $_ = $name;
  2135.         eval $name_mappings;
  2136.         if( $_ ne $old_name ){
  2137.             $name = $_;
  2138.         }
  2139.     }
  2140.     
  2141.     if( $external_mapping ){
  2142.         $old_name = $name;
  2143.         local( $tmp ) = &extmap'map( $name );
  2144.         if( $tmp ne $old_name ){
  2145.             $name = $tmp;
  2146.         }
  2147.     }
  2148.     return $name;
  2149. }
  2150.  
  2151.  
  2152. sub set_timestamps
  2153. {
  2154.     local( $src_path );
  2155.     
  2156.     &msg( "setting timestamps\n" );
  2157.     if( ! $get_file ){
  2158.         &msg( "Cannot set remote timestamps\n" );
  2159.         return;
  2160.     }
  2161.  
  2162.     local( $dest_path, $dest_loc_mapi, $src_rem_mapi,  $rtime );
  2163.     
  2164.     foreach $src_path ( @xfer_src ){
  2165.         $dest_path = shift( @xfer_dest );
  2166.         $dest_loc_mapi = $local_map{ $dest_path };
  2167.         $src_rem_mapi = $remote_map{ $src_path };
  2168.  
  2169.         $rtime = $remote_time[ $src_rem_mapi ];
  2170.         if( $dest_loc_mapi && $local_time[ $dest_loc_mapi ] ne $rtime ){
  2171.             &set_timestamp( $dest_path, $rtime );
  2172.         }
  2173.     }
  2174. }
  2175.  
  2176. sub set_timestamp
  2177. {
  2178.     local( $path, $time ) =  @_;
  2179.     
  2180.     if( $dont_do ){
  2181.         &msg( "Should set time of $path to $time\n" );
  2182.         return;
  2183.     }
  2184.  
  2185.     if( $timestamp || $debug > 2 ){
  2186.         &msg( "Setting time of $path to $time\n" );
  2187.     }
  2188.     utime( $time, $time, $path );
  2189. }
  2190.  
  2191. sub make_dirs
  2192. {
  2193.     local( $thing );
  2194.  
  2195.     return if $dont_do;
  2196.  
  2197.     foreach $thing ( @things_to_make ){
  2198.         if( $thing !~ /^d (.*)/ ){
  2199.             next;
  2200.         }
  2201.         &mkdirs( $1 );
  2202.     }
  2203. }
  2204.  
  2205. sub make_symlinks
  2206. {
  2207.     local( $thing );
  2208.  
  2209.     return if $dont_do;
  2210.  
  2211.     thing:
  2212.     foreach $thing ( @things_to_make ){
  2213.         if( $thing !~ /^l (.*) -> (.*)/ ){
  2214.             next;
  2215.         }
  2216.         local( $dest, $existing ) = ($1, $2);
  2217.         local( $dirpart ) = &dirpart( $dest );
  2218.         if( -e "$dirpart/$existing" ){
  2219.             # symlink to existing file.
  2220.             &mksymlink( $dest, $existing );
  2221.             next;
  2222.         }
  2223.         # The existing file doesn't actually exist!
  2224.         # Has it been compressed, gzipped, split? or worse
  2225.         # compressed/gzipped AND split.  (OK so it could
  2226.         # be another problem, bad symlink on remote host, file
  2227.         # that hasn't been xfer'd yet... but this is as good as
  2228.         # it gets.)
  2229.         local( $p );
  2230.         foreach $p (
  2231.             "\%s.$sys_compress_suffix",
  2232.             "\%s.$gzip_suffix",
  2233.             "\%s/README",
  2234.             "\%s-split/README",
  2235.             "\%s-split.$sys_compress_suffix/README",
  2236.             "\%s-split.$gzip_suffix/README" ){
  2237.             local( $f ) = sprintf( $p, $existing );
  2238.             if( -e $f ){
  2239.                 &msg( "using $p\n" ) if $debug > 2;
  2240.                 &mksymlink( $dest, $f );
  2241.                 next thing;
  2242.             }
  2243.         }
  2244.         if( $make_bad_symlinks ){
  2245.             &msg( "symlink to non-existant file: $dest -> $existing\n" );
  2246.             &mksymlink( $dest, $existing );
  2247.         }
  2248.         else {
  2249.             &msg( "Not symlinking $dest -> $existing\n" );
  2250.         }
  2251.     }
  2252. }
  2253.  
  2254. sub do_all_transfers
  2255. {
  2256.     local( $src_path );
  2257.     
  2258.     if( $#xfer_src < 0 ){
  2259.         &msg( "No files to transfer\n" );
  2260.         return;
  2261.     }
  2262.  
  2263.     foreach $src_path ( @xfer_src ){
  2264.         local( $dest_path, $attribs );
  2265.         local( $srci );
  2266.         
  2267.         if( $get_file ){
  2268.             $srci = $remote_map{ $src_path };
  2269.         }
  2270.         else {
  2271.             $srci = $local_map{ $src_path };
  2272.         }
  2273.  
  2274.         $dest_path = shift( @xfer_dest );
  2275.         $attribs = shift( @xfer_attribs );
  2276.         
  2277.         if( $dont_do ){
  2278.             # Skip trying to get the file.
  2279.             next;
  2280.         }
  2281.  
  2282.         &msg( "Need to $XFER file $src_path as $dest_path ($attribs)\n" ) if $debug > 1;
  2283.  
  2284. #        &msg( "transferring $src_path " );
  2285.         local( $newpath ) =
  2286.             &transfer_file( $src_path, $dest_path,
  2287.                        $attribs, $remote_time[ $srci ] );
  2288.         if( $get_file && $newpath eq '' ){
  2289.             &msg( "Failed to $XFER file $ftp'response\n" );
  2290.             if( $ftp'response =~ /timeout|timed out/i ){
  2291.                 $timeouts++;
  2292.             }
  2293.             if( $ftp'fatalerror || $timeouts > $max_timeouts ){
  2294.                 &msg( "Fatal error talking to site, skipping rest of transfers\n" );
  2295.                 &disconnect();
  2296.                 return;
  2297.             }
  2298.             next;
  2299.         }
  2300.  
  2301.         # File will now have been split up.
  2302.         if( $attribs =~ /s/ ){
  2303. #            &msg( "\n" );
  2304.             next;
  2305.         }
  2306.  
  2307.         if( $newpath ne $src_path ){
  2308. #            &msg( "into $newpath" );
  2309.         }
  2310. #        &msg( "\n" );
  2311.  
  2312.         &set_attribs( $newpath, 'f' );
  2313.  
  2314.         # we can only force time for local files
  2315.         if( $force_times && $get_file ){
  2316.             &set_timestamp( $newpath, $remote_time[ $srci ] );
  2317.         }
  2318.     }
  2319. }
  2320.  
  2321.  
  2322. sub transfer_file
  2323. {
  2324.     local( $src_path, $dest_path, $attribs, $timestamp ) = @_;
  2325.     local( $dir, $file, $temp, $compress, $split, $restart, $mesg, $got_mesg );
  2326.     
  2327.     # Make sure the required directory exists
  2328.     $dir = "";
  2329.     if( $dest_path =~ /^(.+\/)([^\/]+)$/ ){
  2330.         ($dir, $file) = ($1, $2);
  2331.         if( $dest_type[ $dir ] ne 'd' && &mkdirs( $dir ) ){
  2332.             &msg( $log, "Created dir $dir\n" );
  2333.         }
  2334.     }
  2335.     else {
  2336.         $file = $dest_path;
  2337.     }
  2338.     
  2339.     $temp = &filename_to_tempname( $dir, $file );
  2340.     
  2341.     # Interpret the attrib characters
  2342.     if( $attribs !~ /x/ ){
  2343.         # Not an xfer!
  2344.         return '';
  2345.     }
  2346.     if( $attribs =~ /c/ ){
  2347.         $compress = 1;
  2348.         $mesg = " and compress";
  2349.     }
  2350.     if( $attribs =~ /s/ ){
  2351.         $split = 1;
  2352.         $mesg = " and split";
  2353.     }
  2354.     if( $attribs =~ /r/ ){
  2355.         $restart = 1;
  2356.     }
  2357.     
  2358.     if( $vms ){
  2359.         &ftp'type( ($src_file =~ /$vms_xfer_text/i) ? 'A' : 'I' );
  2360.     }
  2361.     
  2362.     if( ! $get_file ){
  2363.         # put the file remotely
  2364.         local( $src_file ) = $src_path;
  2365.         local( $comptemp ) = '';
  2366.  
  2367.         if( $compress ){
  2368.               # No easy way to tell wether this was compressed or not
  2369.               # for now just presume that it is.
  2370.               local( $f ) = $src_file;
  2371.             $f =~ s/($shell_metachars)/\\$1/g;
  2372.               $comptemp = "$big_temp/.out$$";
  2373.             &sys( "$compress_prog < \"$f\" > \"$comptemp\"" );
  2374.               $src_file = $comptemp;
  2375.         }
  2376.         
  2377.         if( ! &ftp'put( $src_file, $temp, $restart ) ){
  2378.             &msg( $log, "Failed to put $src_file: $ftp'response\n" );
  2379.             unlink( $comptemp ) if $comptemp;
  2380.             return '';
  2381.         }
  2382.     
  2383.         unlink( $comptemp ) if $comptemp;
  2384.         if( ! &ftp'rename( $temp, $dest_path ) ){
  2385.             &msg( $log, "Failed to remote rename $temp to $dest_path: $ftp'response\n" );
  2386.             return '';
  2387.         }
  2388.  
  2389.         # Some transfers done
  2390.         $exit_xfer_status |= $exit_xfers;
  2391.         
  2392.         return $dest_path;
  2393.     }
  2394.  
  2395.     # Get a file
  2396.     if( ! &ftp'get( $src_path, $temp, $restart ) ){
  2397.         &msg( $log, "Failed to get $src_path: $ftp'response\n" );
  2398.  
  2399.         # Time stamp the temp file to allow for a restart
  2400.         if( -f $temp ){
  2401.             utime( $timestamp, $timestamp, $temp );
  2402.             # Make sure this file is kept
  2403.             local( $ti ) = $local_map{ $temp };
  2404.             $local_keep[ $ti ] = 1;
  2405.             $local_keep_totals[ 0 ]++;
  2406.         }
  2407.  
  2408.         return '';
  2409.     }
  2410.     
  2411.     # Some transfers done
  2412.     $exit_xfer_status |= $exit_xfers;
  2413.  
  2414.     # delete source file after successful transfer
  2415.     if( $delete_source ){
  2416.         if( &ftp'delete( $path ) ){
  2417.             &msg( $log, "Deleted remote $lpath\n");
  2418.         }
  2419.         else {
  2420.             &msg( $log, "Failed to delete remote $lpath\n");
  2421.         }
  2422.     }
  2423.  
  2424.     if( $compress ){
  2425.         # Prevent the shell from expanding characters
  2426.           local( $f ) = $temp;
  2427.           local( $comp );
  2428.         $f =~ s/($shell_metachars)/\\$1/g;
  2429.           $temp = "$f.$compress_suffix";
  2430.           # Am I doing compress to gzip conversion?
  2431.            if( $src_path =~ /$compress_conv_patt/ &&
  2432.               $compress_suffix eq $gzip_suffix ){
  2433.             $comp = "$sys_compress_prog -d < \"$f\" | $gzip_prog > \"$temp\"";
  2434.           }
  2435.           else {
  2436.             $comp = "$compress_prog < \"$f\" > \"$temp\"";
  2437.           }
  2438.           &sys( $comp );
  2439.         $temp =~ s/\\($shell_metachars)/$1/g;
  2440.         $f =~ s/\\($shell_metachars)/$1/g;
  2441.           unlink( $f );
  2442.       }
  2443.  
  2444.     local( $filesize ) = &filesize( $temp );
  2445.     local( $sizemsg ) = $filesize;
  2446.     local( $srcsize ) = $remote_size[ $remote_map{ $src_path } ];
  2447.     if( $srcsize > $sizemsg && !$compress ){
  2448.         # should never happen, right?  right ...
  2449.         $sizemsg .= " (file shrunk from $srcsize!)";
  2450.     }
  2451.     elsif( $srcsize < $sizemsg ){
  2452.         # compression wasn't such a great idea
  2453.         $sizemsg .= " (file grew from $srcsize!)";
  2454.     }
  2455.  
  2456.     # Ok - chop it up into bits!
  2457.     if( $split ){
  2458.         local( $time ) = 0;
  2459.         if( $force_times ){
  2460.             $time = $remote_time[ $remote_map{ $src_path } ];
  2461.         }
  2462.         &bsplit( $temp, $dest_path, $time );
  2463.         unlink( $temp );
  2464.         $got_mesg .= " and split";
  2465.     }
  2466.     else {
  2467.         rename( $temp, $dest_path );
  2468.     }
  2469.  
  2470.     local( $as ) = '';
  2471.     if( $src_path ne $dest_path ){
  2472.         $as = " as $dest_path";
  2473.     }
  2474.     &msg( $log, "Got $src_path$as$got_mesg $sizemsg\n" );
  2475.     # Make sure to keep what you just got!  It may/may no have
  2476.     # been compressed or gzipped..
  2477.     local( $locali ) = $local_map{ $dest_path };
  2478.     $local_keep[ $locali ] = 1;
  2479.  
  2480.     &log_upload( $src_path, $dest_path, $got_mesg, $filesize );
  2481.  
  2482.     return( $dest_path );
  2483. }
  2484.  
  2485. sub filename_to_tempname
  2486. {
  2487.     local( $dir, $file ) = @_;
  2488.  
  2489.     # dir 
  2490. # LIMITED NAMELEN
  2491. # if you are really limited in pathname length then
  2492. # change the .in. to just .
  2493.     return "$dir.in.$file.";
  2494. }
  2495.  
  2496.  
  2497. # Open, write, close - to try and ensure that the log will allways be filled
  2498. # in.
  2499. sub log_upload
  2500. {
  2501.     local( $src_path, $dest_path, $got_mesg, $size ) = @_;
  2502.  
  2503.     if( ! $upload_log ){
  2504.         return;
  2505.     }
  2506.  
  2507.     if( ! open( ulog, ">>$upload_log" ) ){
  2508.         print STDERR "Cannot write to $upload_log\n";
  2509.         return;
  2510.     }
  2511.  
  2512.     &myflock( 'ulog', $LOCK_EX );
  2513.     print ulog "$site:$remote_dir/$src_path -> $local_dir/$dest_path $size ";
  2514.     if( $got_mesg ){
  2515.         print ulog "($got_mesg)";
  2516.     }
  2517.     print ulog "\n";
  2518.     &myflock( 'ulog', $LOCK_UN );
  2519.     close( ulog );
  2520. }
  2521.  
  2522. sub do_deletes
  2523. {
  2524.     local( *src_paths,
  2525.         *src_map,
  2526.          *src_type, *src_keep,
  2527.           *src_totals, *src_keep_totals ) = @_;
  2528.     local( $files_to_go, $dirs_to_go );
  2529.     
  2530.     if( ! ($do_deletes || $save_deletes) ){
  2531.         return;
  2532.     }
  2533.     
  2534.     local( $src_path, $i );
  2535.     local( $orig_do_deletes ) = $do_deletes;
  2536.     local( $orig_save_deletes ) = $save_deletes;
  2537.  
  2538.     local( $del_patt ) = $delete_patt;
  2539.     if( $delete_get_patt ){
  2540.         $del_patt = $get_patt;
  2541.     }
  2542.     
  2543.     $files_to_go = $src_totals[ 1 ] - $src_keep_totals[ 1 ];
  2544.     $dirs_to_go = $src_totals[ 0 ] - $src_keep_totals[ 0 ];
  2545.  
  2546.     # Adjust totals by considering del_patt
  2547.     for( $i = $#src_paths; $i >= 0; $i-- ){
  2548.         $src_path = $src_paths[ $i ];
  2549.         $srci = $i + 1;
  2550.  
  2551.         if( !$src_keep[ $srci ] && $src_path !~ /$del_patt/ ){
  2552.             if( $src_type[ $srci ] =~ "d" ){
  2553.                 $dirs_to_go--;
  2554.             }
  2555.             else {
  2556.                 $files_to_go--;
  2557.             }
  2558.         }
  2559.     }
  2560.  
  2561.     # Check out file deletions
  2562.     if( $max_delete_files =~ /^(\d+)\%$/ ){
  2563.         # There is a % in the value - so its a percentage
  2564.         local( $per ) = $1;
  2565.         if( $per <= 0 || 100 < $per ){
  2566.             &msg( "silly percentage $max_delete_files, not deleting\n" );
  2567.             $do_deletes = 0;
  2568.             $save_deletes = 0;
  2569.         }
  2570.         else {
  2571.             # Don't do more than this percentage of files
  2572.             # Convert max_delete_files into the number of files
  2573.             $max_delete_files =
  2574.                 int( $src_totals[ 1 ] * $max_delete_files /100 );
  2575.         }
  2576.     }
  2577.     if( $files_to_go > $max_delete_files ){
  2578.         &msg( "Too many files to delete, not actually deleting ($files_to_go > $max_delete_files)\n" );
  2579.         $do_deletes = 0;
  2580.         $save_deletes = 0;
  2581.     }
  2582.  
  2583.     # Check out directory deletions
  2584.     if( $max_delete_dirs =~ /^(\d+)%$/ ){
  2585.         # There is a % in the value - so its a percentage
  2586.         local( $per ) = $1;
  2587.         if( $per <= 0 || 100 < $per ){
  2588.             &msg( "silly percentage $max_delete_dirs, not deleting\n" );
  2589.             $do_deletes = 0;
  2590.             $save_deletes = 0;
  2591.         }
  2592.         else {
  2593.             # Don't do more than this percentage of dirs
  2594.             # Convert max_delete_dirs into the number of dirs
  2595.             $max_delete_dirs =
  2596.                 int( $src_totals[ 0 ] * $max_delete_dirs / 100 );
  2597.         }
  2598.     }
  2599.  
  2600.     if( $dirs_to_go > $max_delete_dirs ){
  2601.         &msg( "Too many directories to delete, not actually deleting ($dirs_to_go > $max_delete_dirs)\n" );
  2602.         $do_deletes = 0;
  2603.         $save_deletes = 0;
  2604.     }
  2605.  
  2606.     # Scan the list backwards so subdirectories are dealt with first
  2607.     for( $i = $#src_paths; $i >= 0; $i-- ){
  2608.         $src_path = $src_paths[ $i ];
  2609.         $srci = $i + 1;
  2610.     
  2611.         if( $src_keep[ $srci ] ){
  2612.             # Keep this for sure;
  2613.             &msg( "Keeping: $src_path\n" ) if $debug > 3;
  2614.             next;
  2615.         }
  2616.  
  2617.         if( $src_path !~ /$del_patt/ ){
  2618.             &msg( "   not in del_patt: $src_path\n" ) if $debug > 1;
  2619.             next;
  2620.         }
  2621.  
  2622.         if( $delete_excl && $src_path =~ /$delete_excl/ ){
  2623.             &msg( "   do not delete: $src_path\n" ) if $debug > 1;
  2624.             next;
  2625.         }
  2626.  
  2627.         if( $save_deletes && $save_dir =~ m,$cwd/(.*), ){
  2628.             local( $save_dir_tail ) = $1;
  2629.             if( $save_dir_tail && $src_path =~ m,$save_dir_tail/*, ){
  2630.                 next;
  2631.             }
  2632.         }
  2633.  
  2634.         if( $save_deletes ){
  2635.             &save_delete( $src_path, $src_type[ $srci ] );
  2636.         }
  2637.         else {
  2638.             &do_delete( $src_path, $src_type[ $srci ] );
  2639.         }
  2640.     }
  2641.     
  2642.     $do_deletes = $orig_do_deletes;
  2643.     $save_deletes = $orig_save_deletes;
  2644. }
  2645.         
  2646. # Move aside the given file.  Kind is 'd' for dirs and 'f' for files.
  2647. sub save_delete
  2648. {
  2649.     local( $save, $kind ) = @_;
  2650.  
  2651.     local( $real_save_dir, $save_dest );
  2652.     eval "\$real_save_dir = \"$save_dir\"";
  2653.  
  2654.  
  2655.     if( ! $get_file ){
  2656.         &msg( "NEED TO implement remote save_deletes\n" );
  2657.         return;
  2658.     }
  2659.     
  2660.     $save_dest = "$real_save_dir/$save";
  2661.  
  2662.     if( $dont_do ){
  2663.         &msg( "save_delete $save to $save_dest\n" );
  2664.         return;
  2665.     }
  2666.  
  2667.     if( $kind eq 'd' ){
  2668.         $save_dest =~ s,/+$,,;
  2669.         
  2670.         # Make sure it exists
  2671.         &save_mkdir( $save_dest );
  2672.             
  2673.         # Zap the original
  2674.         if( rmdir( $save ) == 1 ){
  2675.             &msg( $log, "Removed directory $save\n" );
  2676.         }
  2677.         else {
  2678.             &msg( $log, "UNABLE TO REMOVE DIRECTORY $save\n" );
  2679.         }
  2680.         return;
  2681.     }
  2682.  
  2683.     # Save a file
  2684.  
  2685.     # Make the directories under $save_dir
  2686.     local( $dirname );
  2687.     $dirname = $save_dest;
  2688.     $dirname =~ s/\/[^\/]+$//;
  2689.     # Make sure the directory exists to mv the file into.
  2690.     &save_mkdir( $dirname );
  2691.         
  2692.     if( rename( $save, $save_dest ) == 1 ){
  2693.         &msg( $log, "Moved $save to $save_dest\n" );
  2694.     }
  2695.     else {
  2696.         system "$mv_prog $save $save_dest";
  2697.         if( ( $? >> 8 ) == 0 ){
  2698.             &msg( $log, "Moved $save to $save_dest\n" );
  2699.         }
  2700.         else {
  2701.             &msg( $log, "UNABLE TO MOVE $save TO $save_dest\n" );
  2702.         }
  2703.     }
  2704. }
  2705.  
  2706. sub save_mkdir
  2707. {
  2708.     local( $dir ) = @_;
  2709.     
  2710.     if( ! -d $dir ){
  2711.         if( &mkdirs( $dir ) ){
  2712.             &msg( $log, "Created save directory $dir\n" );
  2713.         }
  2714.         else {
  2715.             &msg( $log, "UNABLE TO CREATE $dir, aborting saves\n" );
  2716.             $save_deletes = 0;
  2717.         }
  2718.     }
  2719. }
  2720.  
  2721. # Delete the given file.  Kind is 'd' for dirs and 'f' for files.
  2722. sub do_delete
  2723. {
  2724.     local( $del, $kind ) = @_;
  2725.     
  2726.     if( $dont_do ){
  2727.         &msg( "delete $del\n" );
  2728.         return;
  2729.     }
  2730.  
  2731.     if( $kind eq 'd' ){
  2732.         $del =~ s,/+$,,;
  2733.         if( $do_deletes ){
  2734.             if( $get_file ){
  2735.                 &msg( $log, "rmdir $cwd/$del\n" );
  2736.                 rmdir( "$cwd/$del" ) ||
  2737.                     &msg( $log, "rmdir $cwd/$del failed: $!\n" );
  2738.             }
  2739.             else {
  2740.                 &msg( $log, "Cannot delete remote directories\n" );
  2741.             }
  2742.         }
  2743.         else {
  2744.             if( $get_file ){
  2745.                 &msg( $log, "NEED TO rmdir $cwd/$del\n" );
  2746.             }
  2747.             else {
  2748.                 &msg( $log, "NEED TO ftp'deldir $del\n" );
  2749.             }
  2750.         }
  2751.         return;
  2752.     }    
  2753.  
  2754.     # Deleting a file.
  2755.     if( $do_deletes ){
  2756.         if( $get_file ){
  2757.             &msg( $log, "unlink $cwd/$del\n" );
  2758.             unlink( "$cwd/$del" ) ||
  2759.                 &msg( $log, "unlink $cwd/$del failed: $!\n" );
  2760.         }
  2761.         else {
  2762.             &msg( $log, "delete $cwd/$del\n" );
  2763.             &ftp'delete( "$cwd/$del" ) &&
  2764.                 &msg( $log, "ftp delete $cwd/$del failed\n" );
  2765.         }
  2766.     }
  2767.     else {
  2768.         if( $get_file ){
  2769.             &msg( $log, "NEED TO unlink $cwd/$del\n" );
  2770.         }
  2771.         else {
  2772.             &msg( $log, "NEED TO ftp'delete $del\n" );
  2773.         }
  2774.     }
  2775. }
  2776.  
  2777. sub filesize
  2778. {
  2779.     local( $fname ) = @_;
  2780.  
  2781.     if( ! -f $fname ){
  2782.         return -1;
  2783.     }
  2784.  
  2785.     return (stat( _ ))[ 7 ];
  2786.     
  2787. }
  2788.  
  2789. # Is the value
  2790. sub istrue
  2791. {
  2792.     local( $val ) = @_;
  2793.     
  2794.     return $val eq '1' || $val eq 'yes' || $val eq 'ok' ||
  2795.            $val eq 'true';
  2796. }
  2797.  
  2798. sub mksymlink
  2799. {
  2800.     local( $dest_path, $existing_path ) = @_;
  2801.  
  2802.     if( ! $get_file ){
  2803.         &msg( "Cannot create symlinks on remote systems ($dest_path -> $existing_path)\n" );
  2804.         return;
  2805.     }
  2806.     
  2807.     # make the symlink locally
  2808.  
  2809.     # Zap any exiting file/symlink of that name
  2810.     if( -d $dest_path && ! -l $dest_path ){
  2811.         local( $msg ) = "rmdir( $dest_path ) before symlink";
  2812.         if( ! rmdir( $dest_path ) ){
  2813.             &msg( "$msg failed: $!\n" );
  2814.             return;
  2815.         }
  2816.         elsif( $debug ){
  2817.             &msg( "$msg\n" );
  2818.         }
  2819.     }
  2820.     if( -e $dest_path || -l $dest_path ){
  2821.         local( $msg ) = "unlink( $dest_path ) before symlink";
  2822.         if( ! unlink( $dest_path ) ){
  2823.             &msg( "$msg failed: $!\n" );
  2824.             return;
  2825.         }
  2826.         elsif( $debug ){
  2827.             &msg( "$msg\n" );
  2828.         }
  2829.     }
  2830.  
  2831.     if( (eval 'symlink("","")', $@ eq '') ){
  2832.         local( $status ) = '';
  2833.         if( ! symlink( $existing_path, $dest_path ) ){
  2834.             $status = "Failed to ";
  2835.         }
  2836.         &msg( $log, $status . "symlink $existing_path to $dest_path\n" );
  2837.     }
  2838.     else {
  2839.         &msg( $log, "Tried to create symlink - but not supported locally\n" );
  2840.     }
  2841. }
  2842.  
  2843.  
  2844. # Make a full directory heirarchy
  2845. # returns true if the directory doesn't exist
  2846. sub mkdirs
  2847. {
  2848.     local( $dir ) = @_;
  2849.     local( @dir, $d, $path );
  2850.  
  2851.     # Very often the directory does exist - so return now
  2852.     return 0 if &dir_exists( $dir );
  2853.     
  2854.     # Make sure that the target directory exists
  2855.     @dirs = split( '/', $dir );
  2856.     
  2857.     # the root directory always exists
  2858.     $path = '';
  2859.     if( $dirs[ 0 ] eq '' ){ 
  2860.         shift( @dirs ); 
  2861.         $path = '/';
  2862.     }
  2863.  
  2864.     foreach $d ( @dirs ){
  2865.         $path = $path . $d;
  2866.         if( ! &dir_exists( $path ) ){
  2867.             &msg( "mkdir $path\n" ) if $debug > 2;
  2868.             if( ! &make_dir( $path, 0755 ) ){
  2869.                 &msg( "make_dir($path,0755) failed with $err\n" );
  2870.                 return 0;
  2871.             }
  2872.             &set_attribs( $path, 'd' );
  2873.         }
  2874.         $path .= "/";
  2875.     }
  2876.     return 1;
  2877. }
  2878.  
  2879. # return 0 on error, 1 on success
  2880. sub make_dir
  2881. {
  2882.     local( $dir, $mode ) = @_;
  2883.     local( $val );
  2884.  
  2885.     if( $get_file ){
  2886.         # make a local directory
  2887.         if( -e $dir || -l $dir ){
  2888.             unlink( $dir );
  2889.         }
  2890.         $val = mkdir( $dir, $mode );
  2891.         $err = "$!";
  2892.     }
  2893.     else {
  2894.         # make a remote directory
  2895.         $val = &ftp'mkdir( $dir );
  2896.  
  2897.         # The mkdir might have failed due to bad mode
  2898.         # So try to chmod it anyway
  2899.         if( $remote_has_chmod ){
  2900.             $val = &ftp'chmod( $dir, $mode );
  2901.         }
  2902.     }
  2903.  
  2904.     return $val;
  2905. }
  2906.  
  2907. # return 1 if $dir exists, 0 if not
  2908. sub dir_exists
  2909. {
  2910.     local( $dir ) = @_;
  2911.     local( $val );
  2912.  
  2913.     if( $get_file ){
  2914.         # check if local directory exists
  2915.         $val = (-d $dir);
  2916.     }
  2917.     else {
  2918.         # check if remote directory exists
  2919.         local($old_dir) = &ftp'pwd();        
  2920.         
  2921.         $val = &ftp'cwd($dir);
  2922.  
  2923.         # go back to the original directory
  2924.         &ftp'cwd($old_dir) || die "Cannot cd to original remote directory";
  2925.     }
  2926.     return $val;
  2927. }
  2928.  
  2929. # Set file/directory attributes
  2930. sub set_attribs
  2931. {
  2932.     local( $path, $type ) = @_;
  2933.     local( $mode );
  2934.     
  2935.     if( $get_file ){
  2936.         local( $pathi ) = $remote_map{ $path };
  2937.         $mode = $remote_mode[ $pathi ];
  2938.     }
  2939.     else {
  2940.         local( $pathi ) = $local_map{ $path };
  2941.         $mode = $local_mode[ $pathi ];
  2942.     }
  2943.  
  2944.     # If I can't figure out the mode or I'm not copying it
  2945.     # use the default
  2946.     if( !$mode_copy || !$mode ){
  2947.         if( $type eq 'f' ){
  2948.             $mode = $file_mode;
  2949.         }
  2950.         elsif( $type eq 'd' ){
  2951.             $mode = $dir_mode;
  2952.         }
  2953.     }
  2954.  
  2955.     # Convert from octal
  2956.     $mode = oct( $mode ) if $mode =~ /^0/;
  2957.  
  2958.     if( $get_file ){
  2959.         # Change local
  2960.  
  2961.         chmod $mode, $path;
  2962.  
  2963.         if( $user ne '' && $group ne '' ){
  2964.             local( $uid, $gid );
  2965.             if( $user =~ /^\d+$/ ){
  2966.                 # User is just a number - presume it is the uid
  2967.                 $uid = $user;
  2968.             }
  2969.             else {
  2970.                 $uid = (getpwnam( $user ))[ 2 ];
  2971.             }
  2972.             if( $group =~ /\d+$/ ){
  2973.                 # Group is just a number - presume it is the gid
  2974.                 $gid = $group;
  2975.             }
  2976.             else {
  2977.                 $gid = (getgrnam( $group ))[ 2 ];
  2978.             }
  2979.  
  2980.             chown $uid, $gid, $path;
  2981.         }
  2982.     }
  2983.     else {
  2984.         # change the remote file
  2985.         if( $remote_has_chmod ){
  2986.             &ftp'chmod( $path, $mode );
  2987.         }
  2988.     }
  2989. }
  2990.  
  2991.  
  2992. sub get_passwd
  2993. {
  2994.     local( $user ) = @_;
  2995.     local( $pass );
  2996.  
  2997.     # prompt for a password
  2998.     $SIG{ 'INT' } = 'IGNORE';
  2999.     $SIG{ 'QUIT' } = 'IGNORE';
  3000.  
  3001.     system "stty -echo </dev/tty >/dev/tty 2>&1";
  3002.     print "Password for $user: ";
  3003.  
  3004.     $pass = <STDIN>;
  3005.     print "\n";
  3006.     chop( $pass );
  3007.  
  3008.     system "stty echo </dev/tty >/dev/tty 2>&1";
  3009.  
  3010.     $SIG{ 'INT' } = 'DEFAULT';
  3011.     $SIG{ 'QUIT' } = 'DEFAULT';
  3012.     
  3013.     return $pass;
  3014. }
  3015.  
  3016. sub compare_times
  3017. {
  3018.     # Try and allow for time zone changes (eg when a site
  3019.     # switches from daylight saving to non daylight saving)
  3020.     # by ignoring differences of exactly one hour
  3021.  
  3022.     local( $t1, $t2 ) = @_;
  3023.     local( $diff ) = ($t1 > $t2 ? $t1 - $t2 : $t2 - $t1);
  3024.  
  3025.     return ($t1 > $t2) && ($diff != 3600);
  3026. }
  3027.  
  3028. sub create_assocs
  3029. {
  3030.     local( $map );
  3031.  
  3032.     &delete_assocs();
  3033.  
  3034.     &msg( "creating assocs ...\n" ) if $debug > 2;
  3035.     foreach $map ( @assocs ){
  3036.         eval "\$$map = \"\$tmp/$map.$$\"";
  3037.         eval "dbmopen( $map, \$$map, 0644 )";
  3038.     }
  3039.     &msg( "creating assocs done\n" ) if $debug > 2;
  3040. }
  3041.  
  3042. sub delete_assocs
  3043. {
  3044.     local( $map );
  3045.  
  3046.     &msg( "deleting assocs ...\n" ) if $debug > 2;
  3047.     foreach $map ( @assocs ){
  3048.         eval "\$$map = \"\$tmp/$map.$$\"";
  3049.         eval "dbmclose( $map )";
  3050.         &unlink_dbm( eval "\$$map" );
  3051.         eval "\%$map = ()";
  3052.     }
  3053.     &msg( "deleting assocs done\n" ) if $debug > 2;
  3054. }
  3055.  
  3056. sub unlink_dbm
  3057. {
  3058.     local( $file ) = @_;
  3059.     unlink "$file.pag";
  3060.     unlink "$file.dir";
  3061. }
  3062.  
  3063. # Chop the tmp file up
  3064. sub bsplit
  3065. {
  3066.     local( $temp, $dest_path, $time ) = @_;
  3067.     local( $dest_dir ) = "$dest_path-split";
  3068.     local( $bufsiz ) = 512;
  3069.     local( $buffer, $in, $sofar );
  3070.  
  3071.     &msg( "Splitting up $temp into $dest_dir/ ($time)\n" ) if $debug;
  3072.  
  3073.     # Stomp on the original directories
  3074.     local( $d ) = $dest_dir;
  3075.     $d =~ s/($shell_metachars)/\\$1/g;
  3076.     &sys( "$rm_prog -rf \"$d\"" );
  3077.  
  3078.     &mkdirs( $dest_dir );
  3079.  
  3080.     local( $index ) = "00";
  3081.     local( $part );
  3082.     open( tmp, $temp ) || die "Cannot open $temp!";
  3083.     $sofar = $split_chunk; # Force a new file
  3084.     while( ($in = sysread( tmp, $buffer, $bufsiz )) > 0 ){
  3085.         if( $sofar >= $split_chunk ){
  3086.             if( $part ){
  3087.                 close( part );
  3088.                 if( $time ){
  3089.                     &set_timestamp( $part, $time );
  3090.                 }
  3091.             }
  3092.             $index++;
  3093.             $part = "$dest_dir/part$index";
  3094.             &msg( "creating $part\n" ) if $debug;
  3095.             open( part, ">$part" ) || die "Cannot create $part";
  3096.             # Make sure to keep this!
  3097.             local( $locali ) = $local_map{ $part };
  3098.             $local_keep[ $locali ] = 1;
  3099.             $sofar = 0;
  3100.         }
  3101.         if( ($out = syswrite( part, $buffer, $in )) != $in ){
  3102.             die "Failed to write data to $part";
  3103.         }
  3104.         $sofar += $in;
  3105.     }
  3106.     close( part );
  3107.     if( $time ){
  3108.         &set_timestamp( $part, $time );
  3109.     }
  3110.     close( tmp );
  3111.  
  3112.     # Generate a readme file about what is in the split directory
  3113.     local( $readme ) = "$dest_dir/README";
  3114.     open( readme, ">$readme" ) || die "Cannot create $readme";
  3115.     print readme "This directory contains a splitup version of $dest_path\n";
  3116.     print readme "to recreate the original simply concatenate all the\n";
  3117.     print readme "parts back together.\n\nChecksums are:\n\n";
  3118.     close readme;
  3119.     &sys( "(cd \"$d\" ; $sum_prog part*)>> $readme" );
  3120. }
  3121.  
  3122. sub sys
  3123. {
  3124.     local( $com ) = @_;
  3125.     &msg( "$com\n" ) if $debug > 2;
  3126.     system( $com );
  3127. }
  3128.  
  3129. # Set up an associative array given all an array of keys.
  3130. # @fred = ( 'a' );
  3131. # &set_assoc_from_array( *fred )
  3132. # Creates => $fred{ 'a' } = 1
  3133. #
  3134. sub set_assoc_from_array
  3135. {
  3136.     local( *things ) = @_;
  3137.     foreach $thing ( @things ){
  3138.         $things{ $thing } = 1;
  3139.     }
  3140. }
  3141.  
  3142. sub find_prog
  3143. {
  3144.     local( $prog ) = @_;
  3145.     local( $path ) = $ENV{ 'PATH' } . ':' . $extra_path;
  3146.     
  3147.     foreach $dir ( split( /:/, $path ) ){
  3148.         local( $path ) = "$dir/$prog";
  3149.         if( -x $path ){
  3150.             return $path;
  3151.         }
  3152.     }
  3153.     return '';
  3154. }
  3155.  
  3156. sub real_dir_from_path
  3157. {
  3158.     local( $program ) = @_;
  3159.     local( @prog_path ) = split( m:/: , $program );    # dir collection
  3160.     local( $dir );
  3161.  
  3162.     while( -l $program ){                # follow symlink
  3163.         $program = readlink( $program );
  3164.         if( $program =~ m:^/: ){        # full path?
  3165.             @prog_path = ();        # start dir collection anew
  3166.         }
  3167.         else {
  3168.             pop( @prog_path );        # discard file name
  3169.         }
  3170.         push( @prog_path, split( m:/:, $program ) );# add new parts
  3171.         $program = join( '/', @prog_path );  # might be a symlink again...
  3172.     }
  3173.     pop( @prog_path );
  3174.     $dir = join( '/', @prog_path );
  3175.  
  3176.     if( ! $dir ){
  3177.         $dir = '.';
  3178.     }
  3179.     
  3180.     return $dir;
  3181. }
  3182.  
  3183. sub msg
  3184. {
  3185.     local( $todo, $msg );
  3186.  
  3187.     if( $#_ == 1 ){
  3188.         ($todo, $msg) = @_;
  3189.     }
  3190.     else {
  3191.         $todo = 0;
  3192.         $msg = @_[ 0 ];
  3193.     }
  3194.  
  3195.     # Assign to $0 so when you do a 'ps' it says this!
  3196.     if( $package ){
  3197.         $0 =  "mirror $package:$site:$remote_dir $msg";
  3198.     }
  3199.     else {
  3200.         $0 = "mirror $msg";
  3201.     }
  3202.  
  3203.     if( $todo & $log ){
  3204.         push( @log, $msg );
  3205.     }
  3206. # Not sure about this one.  always print the message even if its a log msg.
  3207. #    else {
  3208.         print $msg;
  3209. #    }
  3210. }
  3211.  
  3212. sub to_bytes
  3213. {
  3214.     local( $size ) = @_;
  3215.     if( $size =~ /^(\d+)\s*(k|b|m)s*$/i ){
  3216.         $size = $1;
  3217.         if( $2 =~ /[mM]/ ){
  3218.             $size *= (1024*1024);
  3219.         }
  3220.         elsif( $2 =~ /[bB]/ ){
  3221.             $size *= 512;
  3222.         }
  3223.         elsif( $2 =~ /[kK]/ ){
  3224.             $size *= 1024;
  3225.         }
  3226.     }
  3227.     return $size;
  3228. }
  3229.  
  3230. # Given a unix filename map it into a vms name.
  3231. # $kind is 'f' for files and 'd' for directories
  3232. sub unix2vms
  3233. {
  3234.     local( $v, $kind ) = @_;
  3235.  
  3236.     if( $v eq '.' || $v eq '/' ){
  3237.         return "[]";
  3238.     }
  3239.  
  3240.     if( $vms_dir ){
  3241.         $v = $vms_dir . '/' . $v;
  3242.     }
  3243.  
  3244.     if( $kind eq 'f' ){
  3245.         # Map a/b/c.txt into [a.b]c.txt
  3246.         if( $v =~ m,(.*)/([^/]+), ){
  3247.             local( $dir, $rest ) = ($1, $2);
  3248.             $dir =~ s,/,.,g;
  3249.             $v = "[$dir]$rest";
  3250.         }
  3251.     }
  3252.     else {
  3253.         # Map a/b/c into [a.b.c]
  3254.         $v =~ s,/,.,g;
  3255.         $v = "[$v]";
  3256.     }
  3257.     return $v;
  3258. }
  3259.  
  3260. sub dirpart
  3261. {
  3262.     local( $path ) = @_;
  3263.     if( $path =~ m:/: ){
  3264.         $path =~ s:^(.*)/[^/]+$:$1:;
  3265.     }
  3266.     else {
  3267.         $path = '.';
  3268.     }
  3269.     return $path;
  3270. }
  3271.  
  3272. # Given a filename (not a directory) and what path it symlinks to
  3273. # return a, hopefully, non-relative pathname that the symlink
  3274. # really points to.  This is so it can be used to index into the $src_path
  3275. # map.
  3276. sub expand_symlink
  3277. {
  3278.     local( $orig_path, $points_to ) = @_;
  3279.     local( $dirpart ) = &dirpart( $orig_path );
  3280.  
  3281.     return &flatten_path( "$dirpart/$points_to" );
  3282. }
  3283.  
  3284. # flatten out the effects of dir/.. and /./
  3285. # The problem is not flattening out ../.. into nothing!  Hence
  3286. # the contortions below.
  3287. sub flatten_path
  3288. {
  3289.     local( $path ) = @_;
  3290.     local( $changed ) = 1;
  3291.     local( $i );
  3292.     
  3293.     local( $rooted ) = $path =~ m:^/:;
  3294.     
  3295.     $path =~ s:^/::;
  3296.     $path =~ s:(^|/)\.(/|$)::g;
  3297.  
  3298.     while( $changed ){
  3299.         local( $in ) = $path;
  3300.         local( @parts ) = split( /\/+/, $path );
  3301.         for( $i = 0; $i <= $#parts; $i++ ){
  3302.             if( $parts[ $i ] eq '.' ){
  3303.                 $parts[ $i ] = undef;
  3304.                 next;
  3305.             }
  3306.             if( $i > 0 && $parts[ $i ] eq '..' &&
  3307.                $parts[ $i - 1 ] && $parts[ $i - 1 ] ne '..' ){
  3308.                 $parts[ $i - 1 ] = $parts[ $i ] = undef;
  3309.                 next;
  3310.             }
  3311.         }
  3312.         $path = '';
  3313.         for( $i = 0; $i <= $#parts; $i++ ){
  3314.             next unless $parts[ $i ];
  3315.             $path .= '/' if $path;
  3316.             $path .= $parts[ $i ];
  3317.         }
  3318.         $changed = $in ne $path;
  3319.     }
  3320.     if( $rooted ){
  3321.         $path = "/$path";
  3322.     }
  3323.     return $path;
  3324. }
  3325.  
  3326.  
  3327. # Fix up a package name.
  3328. # strip trailing and leading ws and replace awkward characters
  3329. # This doesn't guarentee a unique filename.
  3330. sub fix_package
  3331. {
  3332.     local( $package ) = @_;
  3333.     $package =~ s:[\s/']:_:g;
  3334.     return $package;
  3335. }
  3336.  
  3337. sub will_compress
  3338. {
  3339.     $src_type[ $_[1] ] eq 'f' &&
  3340.     $compress_patt && $_[0] =~ /$compress_patt/ &&
  3341.     ( ! $compress_size_floor ||
  3342.       $compress_size_floor < $src_size[ $_[1] ] ) &&
  3343.     !($compress_excl && $_[0] =~ /$compress_excl/i) &&
  3344.     !($compress_suffix eq $gzip_suffix &&
  3345.       $compress_conv_patt && $_[0] =~ /$compress_conv_patt/);
  3346. }
  3347.  
  3348. sub will_split
  3349. {
  3350.      $split_max &&
  3351.     $src_size[ $_[1] ] > $split_max &&
  3352.     $src_type[ $_[1] ] eq 'f' &&
  3353.     $split_patt && $_[0] =~ /$split_patt/;
  3354. }
  3355.  
  3356. sub myflock
  3357. {
  3358.     local( $file, $kind ) = @_;
  3359.  
  3360.     if( ! $can_flock ){
  3361.         return;
  3362.     }
  3363.  
  3364.     eval( "flock( \$file, $kind )" );
  3365.     if( $@ =~ /unimplemented/ ){
  3366.         $can_flock = 0;
  3367.         warn "flock not unavialable, running unlocked\n";
  3368.     }
  3369. }
  3370.  
  3371. sub t2str
  3372. {
  3373.     local( @t );
  3374.     if( $use_timelocal ){
  3375.         @t = localtime( $_[0] );
  3376.     }
  3377.     else {
  3378.         @t = gmtime( $_[0] );
  3379.     }
  3380.     local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = @t;
  3381.  
  3382.     return sprintf( "%02d/%02d/%02d-%02d:%02d:%02d",
  3383.         $year, $mon + 1, $mday, $hour, $him, $sec );
  3384. }
  3385.